# Utils.java

    /*
    ** Module   : Utils.java
    ** Abstract : General purpose utility methods
    **
    ** Copyright (c) 2004-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
    ** 001 ECF 20041122   @18804 Created initial version, consisting of some
    **                           Properties object helper methods.
    ** 002 NVS 20050223   @19935 Added readPassphrase method.
    ** 003 ECF 20050225   @19962 Added printChainedErrors method.
    ** 004 ECF 20050225   @20061 Added findFileInPath method. Generalized and
    **                           moved from findFile in uast.AstGenerator.
    ** 005 GES 20050304   @20196 Added file copying methods.
    ** 006 GES 20050311   @20293 Added isLaterVersion method.
    ** 007 ECF 20050329   @20519 Modified printChainedErrors method. Now
    **                           prints name of throwable type if no message
    **                           is present.
    ** 008 ECF 20050422   @20844 Added indent parameter to printChainedErrors
    **                           method. Indents each message line by the
    **                           specified number of spaces.
    ** 009 GES 20050706   @21667 Added append mode to copyFile().
    ** 010 ECF 20051103   @23222 Added getLog method. Retrieves an Apache
    **                           commons-logging logging object scoped to a
    **                           particular package.
    ** 011 ECF 20051104   @23239 Added describeContext method. Uniquely
    **                           describes current context for debugging or
    **                           logging.
    ** 012 ECF 20060112   @23897 Added verifyDirectoryNode method. Verifies
    **                           existence and class of a required directory
    **                           entry.
    ** 013 GES 20060122   @24013 Added directory query helpers to manage
    **                           the entire process of searching for a
    **                           specified value across a pre-defined
    **                           hierarchy in the directory.
    ** 014 ECF 20060123   @24039 Modified describeContext method. Combine
    **                           session ID, thread ID, and user ID to create
    **                           a unique string.
    ** 015 GES 20060125   @24073 Added directory node search helper.
    ** 016 NVS 20060226   @24749 Added generic method search and invocation
    **                           service invoke().
    ** 017 NVS 20060227   @24771 Added noHelp() method which is a placeholder
    **                           for a real help application.
    ** 018 ECF 20060309   @24998 Added getUnqualifiedName() method. Gets the
    **                           short name of a class or interface.
    ** 019 GES 20060609   @27053 Removed fillInStackTrace() from invoke()
    **                           since this only destroys the exception's
    **                           stack trace (it doesn't affect any chained
    **                           cause's stack trace). It is better to have
    **                           the original record of where the problem
    **                           occurred than to know that we rethrew the
    **                           exception from this point.
    ** 020 GES 20060802   @28296 Created primitive/wrapper conversion helpers
    **                           to simplify list and array processing.
    ** 021 ECF 20060918   @29668 Temporary fix to describeContext(). Handle
    **                           invalid thread ID returned from
    **                           SecurityManager. A better fix would be to
    **                           ensure SecurityManager never returns a null
    **                           thread ID.
    ** 022 GES 20061220   @31807 Removed readPassphrase() and replaced it
    **                           with prompt() that is a simpler and safer
    **                           alternative.
    ** 023 NVS 20070112   @31893 Added uniqueId() method and supporting data
    **                           in form of a context local work area.
    **                           Added methods to work with sets: intersects()
    **                           intersection() and difference().
    ** 024 NVS 20070126   @32010 Added scope param to findDirectoryNodePath()
    **                           method. Now the method can do server level
    **                           searches (/server/<serverId> and
    **                           /server/default paths) and account level
    **                           searches as the original implementation.
    ** 025 NVS 20070404   @32764 Added new method diffIntArrays().
    ** 026 GES 20070716   @34530 Moved file copying related methods here for
    **                           better reuse.
    ** 027 ECF 20071009   @35600 Added getPortForService() and variants of
    **                           other directory convenience methods. The
    **                           former looks up the port number for a given
    **                           service name. The latter add a 'scope'
    **                           parameter to permit looking up values of each
    **                           basic data type by account/group or by
    **                           server.
    ** 028 ECF 20071101   @35897 Changed invoke() signature. Now uses var-args
    **                           instead of an Object array for parameters, if
    **                           any, to be passed to the invoked method.
    ** 029 GES 20080414   @38023 Added helpers for text processing which
    **                           honors the proper charset conversions.
    ** 030 GES 20080421   @38058 Added another charset helper.
    ** 031 CA  20081006   @40061 Added integerCollectionToPrimitive.
    ** 032 CA  20081205   @40820 Added integerMapToPrimitive.
    ** 033 CA  20090413   @41770 Fixed bug in integerMapToPrimitive - index was
    **                           not incremented.
    ** 034 GES 20090422   @41918 Converted to standard string formatting.
    ** 035 GES 20090424   @41975 Import change.
    ** 036 NVS 20090624   @42833 Added new method removeDirectoryNode().
    ** 037 SIY 20090701   @43013 Added scroll() method.
    **                           Removed integerListToPrimitive() which is a copy
    **                           of integerCollectionToPrimitive(). Inferred
    **                           generics in order to improve type checking.
    ** 038 SIY 20090923   @43987 Directory binding in getDirectoryNodeWorker now is
    **                           optional if directory is already bound.
    ** 039 ECF 20101004          Added methods to match a file path case-
    **                           insensitively and to canonicalize a file system
    **                           path.
    ** 040 SIY 20110112          Changes related to new color handling scheme. Minor
    **                           cleanups.
    ** 041 GES 20110623          Fixed case sensitive filename matching helpers.
    ** 042 CA  20130822          Added API to check if two sets match.
    ** 043 HC  20131031          Support methods for Long resource IDs, see issue #2183.
    ** 044 CA  20140206          Improved the directory node lookup APIs, to allow search using an
    **                           explicit account list and to allow fallback to a per-server value, if
    **                           per-account search failed.
    ** 045 OM  20140706          Fixed matchPathCaseInsensitively for Windows filesystem.
    ** 046 GES 20141106          Improved some javadocs and added unimplemented feature logging.
    ** 047 CA  20150302          Add logging if a directory node is found with an unexpected class.
    ** 048 ECF 20150715          Replace StringBuffer with StringBuilder. Deprecated getLog.
    ** 049 GES 20150923          Added causeChainContains().
    ** 050 OM  20151130          Eliminated IOException from canonicalizePath. Declared getLog()
    **                           method as @Deprecated because we are moving towards J2SE logging.
    ** 051 HC  20151213          Improved DIALOG-BOX window parenting logic and related changes.
    ** 052 CA  20151229          If instantiation of entry point fails with an ERROR condition, then
    **                           show the error message.
    ** 053 EVL 20160224          Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 054 IAS 20160229          Added file extension extractor, tryWhileNull utility method and
    **                           InstrumentedMap.
    ** 055 ECF 20160202          Removed deprecated getLog method.
    ** 056 IAS 20160331          Fixed intersect() and isSame() to avoid NPE.
    ** 057 OM  20160422          Added per-project support.
    ** 058 EVL 20170831          Adding helper method to convert string to file size integer value.
    ** 059 ECF 20171121          Modified intersect method to exit quickly if sets are empty.
    **     GES 20171122          Added split() to more efficiently calculate both the differences
    **                           and intersection, when both are needed anyway.
    ** 060 SBI 20180410          Added calculateResourcePath to be used by gui drivers.
    ** 061 SBI 20180521          Added getPathResolver and getSafePathResolver.
    ** 062 SBI 20180531          Added common Utils.getDirectoryNodeMap.
    ** 063 SBI 20180716          Generalized code normalizeZorder and Zkey, moveToTopInClass and
    **                           moveToBottomInClass from AbstractContainer.
    ** 064 HC  20180813          Added double data type to the directory access interface.
    ** 065 HC  20190701          Added methods longToBytesLE and bytesToLongLE.
    ** 066 CA  20190731          Added findFieldWithValue.
    **     CA  20190811          Added collectInterfaces.
    ** 067 ECF 20190619          Refactored replaceSeparator from canonicalizePath.
    ** 068 CA  20191119          Track the instantiating class, to be able to register any buffer to
    **                           its source definition class.
    **     CA  20191212          Added findConstructor, to find the first ctor which matches the
    **                           argument's class or super-class, or any of its super-interfaces.
    ** 069 HC  20200130          Added a method for enumerating nodes.
    ** 070 SBI 20200225          Moved isValidValue to this helper class.
    ** 071 CA  20200122          Javadoc fixes.
    ** 072 HC  20200726          Added enumDirectoryNodes method.
    **     GES 20200922          Minor optimizations in set processing.
    **     CA  20200924          Added Utils.invoke, to execute methods via ReflectASM instead of Method.invoke.
    **     CA  20201003          Use an identity HashSet where possible.
    **     CA  20201011          Added intersect() and containsAll() for RoaringBitmap (fast-access bitmap used
    **                           instead of integer sets).
    **     ME  20210121          Fix longToBytesLE to use little end order.
    **     RFB 20210331          Added Utils.onlyDigits to assist FillIn widget updates. Ref #5210.
    **     RFB 20210402          No need for onlyDigits, as NumberType.parseDoubleTest was added to handle this
    **                           need. Minor javadoc fix, as well. Ref #5210.
    **     SBI 20210413          Added generic getDirectoryNodeBeanList to get simple java bean from the directory settings.
    **         20210414          Added generic searchResourceJars, moved getRefinedPathName from FileSystemOps.
    **     CA  20210407          Throw NPE if if invoking an instance method and the instance is null.
    **     SBI 20210621          Changed getDirectoryNodeBeanList to get properties from node's attributes.
    **         20210701          Added the common code to getOrCreateDefaultFwdTemporaryDirectory(), SupplierWithMemo.
    **     HC  20210727          Fixed doWithBoundDS to bind the supplied directory service if needed.
    **     RFB 20210805          Implemented safe isServer/isClient/isChUI/isGUI for use on client of server.
    **                           Ref #4984.
    **     SBI 20210813          Changed searchResourceJars to make it independent from the server side,
    **                           added getJarPath
    **     ECF 20210916          describeContext now returns only the thread name if not running on the server.
    **     CA  20210928          Added getOrCreateTemporaryDirectory.
    **     CA  20211004          Automatically delete the created temporary folder (and its content), on JVM
    **                           shutdown.
    **     CA  20211019          The temporary folder name must be unique for each context (previously was a
    **                           static variable).
    **     VVT 20211020          getOrCreateTemporaryDirectory() fixed: cleanup hook must be added only once per directory.
    **                           (ref. #5744).
    **     VVT 20211021          Restored statements unnecessarily nested within else clause, removed
    **                           earlier in the previous change.
    **     CA  20211025          Added dfsClassHierarchy.
    **     HC  20220307          Added disableClientSSLCertificateValidation to allow disabling of SSL
    **                           certificate validation on HTTPS client connections.
    **     SBI 20220427          Added int convertCodepoint(int, String), getCodepoint(int, String) and
    **                           changed char toChar(int) to use cpinternal charset.
    **     RFB 20220504          Minor javadoc fixes.
    **     SBI 20220504          Fixed toChar(int, String) to return -1 if key code is not represented by
    **                           the given char set.
    **     TJD 20220504          Java 11 compatibility minor changes
    ** 073 SBI 20230224          Added getCharsetOverride(String charsetName).
    ** 074 SBI 20230411          Added isEndPointAvailable.
    ** 075 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 076 CA  20230601          Changed 'collectInterfaces' to perform a BFS walk of the type hierarchy.  Also,
    **                           this fixes a bug where only the direct interfaces were collected.
    ** 077 GBB 20230608          pollAll method added to empty a Queue into a List.
    ** 078 GBB 20230616          Remove stacktrace from log msg `not available host:port` to declutter log files.
    ** 079 GBB 20230620          createThreadName method added (originally defined in LogHelper).
    ** 080 TT  20230626          Added support for an explicitly set temp directory (-T).
    **                           getOrCreateTemporaryDirectory() now accepts an additional parameter called explicitFolderName.
    **                           Added the methods getTmpDir() and tryToSetTempDir().
    **                           Added a Boolean version of getDirectoryNodeBoolean().
    ** 081 GBB 20230825          SecurityManager context & session methods calls updated.
    **                           getCustomPackageRoot added.
    ** 082 TT  20230915          Error handling using PostInitErrorManager when trying to setTemporaryDirectory().
    ** 083 GBB 20240404          JarResource class and searchResourceJars() moved to JarUtil. logProcessError()
    **                           moved here from ClientCore to be reused and improved to not block on read.
    **                           getCustomPackageRoot() to use OS independent file separators.
    ** 084 GBB 20240515          Adding convenience overload for getDirectoryNodeStrings().
    ** 085 GBB 20240613          Process identifier added to logs of process error handling.
    ** 086 GBB 20240709          Removing redundant getDirectoryNodeStrings overload method.
    ** 087 GBB 20240718          Handle more gracefully setting project token.
    ** 088 CA  20240722          Added 'getDirectoryNodeMap' which allows a 'null' DirectoryService argument.
    ** 089 ICP 20240722          Modified shutdown hook registration.
    ** 090 VVT 20240826          SourceNameMapper.convertJavaProg() renamed. See #8613-16.
    ** 091 GBB 20240828          Moving OSResourceManager and FileSystem to osresource package.
    ** 092 SBI 20240902          Fixed moveToTop[Bottom]InClass according to its internal logic, removed dead code.
    ** 093 CA  20240910          Added 'isRunningJava8', used to enable 'net:socket:nio' by default.
    ** 094 SBI 20240930          Added toURI.
    ** 095 SBI 20250126          Removed usages of I18nOPS from toChar(.)
    ** 096 AS  20250529          Added pathCache to cache the properly formatted case sensitive image path string.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import java.io.*;
    import java.security.*;
    import java.security.cert.*;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.function.*;
    import java.util.logging.*;
    import java.util.stream.*;

    import com.goldencode.cache.ExpiryCache;
    import com.goldencode.p2j.main.OrderedShutdownHooks;
    import com.goldencode.p2j.persist.CacheManager;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.p2j.util.osresource.*;
    import com.goldencode.p2j.util.osresource.FileSystem;
    import com.goldencode.util.*;

    import org.roaringbitmap.*;
    import org.roaringbitmap.longlong.*;

    import java.net.*;
    import java.nio.*;
    import java.nio.charset.*;
    import java.nio.file.*;
    import java.lang.invoke.*;
    import java.lang.reflect.*;

    import com.esotericsoftware.reflectasm.*;
    import com.goldencode.p2j.*;
    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.directory.*;
    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.ui.LogicalTerminal;
    import com.goldencode.p2j.ui.ZOrderClass;
    import com.goldencode.p2j.ui.chui.ThinClient;

    import javax.net.ssl.*;

    /**
     * Collection of useful, static, utility methods for various tasks.
     */
    public final class Utils
    {
       /** Define search modes for the directory node lookups. */
       public static enum DirScope
       {
          SERVER,
          ACCOUNTS,
          BOTH
       }

       /** Global cache of ReflectASM information for {@link #invoke(Method, Object, Object...)}. */
       private static final Map<Class<?>, MethodData> METHOD_DATA = new ConcurrentHashMap<>();

       /** logger */
       private static final CentralLogger LOG = CentralLogger.get(Utils.class.getName());

       /** Stores context-local state variables. */
       private static ContextContainer work = new ContextContainer();

       /** Logging token that ensures unimplemented feature logging occurs only once. */
       private static Object token = null;

       /** A cache of single-arg constructors in all possible {@link BaseDataType} sub-classes. */
       private static Map<String, Constructor<?>> bdtConstructors;

       /** A cache to store the properly formatted case sensitive image path string. */
       private static ExpiryCache<String, String> pathCache;

       static
       {
          bdtConstructors = new HashMap<>();

          try
          {
             for (ParmType ptype : ParmType.values())
             {
                Class<?> cls = ptype.getParamType();
                if (BaseDataType.class.isAssignableFrom(cls) && cls != BaseDataType.class)
                {
                   Constructor<?>[] ctors = cls.getConstructors();
                   for (Constructor<?> ctor : ctors)
                   {
                      Class<?>[] ptypes = ctor.getParameterTypes();
                      if (ptypes.length != 1)
                      {
                         continue;
                      }

                      Class<?> argType = ptypes[0];
                      String key = cls.toString() + "#" + argType.toString();

                      ctor.setAccessible(true);
                      bdtConstructors.put(key, ctor);
                   }
                }
             }
          }
          catch (Throwable t)
          {
             // within PL/Java, this will not be able to initialize
             LOG.warning("Could not populate BDT constructors!");
          }

          bdtConstructors = Collections.unmodifiableMap(bdtConstructors);
       }

       /**
        * Private constructor to prevent instances of this class from being
        * created.
        */
       private Utils()
       {
       }

       /**
        * Check if the current Java version is java 8.
        *
        * @return   See above.
        */
       public static boolean isRunningJava8()
       {
          String ver = System.getProperty("java.version");
          return ver.startsWith("1.8");
       }

       /**
        * Find the first single-argument constructor which has a single parameter with the argument's
        * type or any of its super-classes or super-interfaces.
        *
        * @param    cls
        *           The class where to search for the c'tor.
        * @param    arg
        *           The argument's type.
        *
        * @return   The found c'tor.
        */
       public static Constructor<?> findConstructor(Class<?> cls, Class<?> arg)
       {
          String key = cls.toString() + "#";

          Class<?> parent = arg;
          while (parent != null)
          {
             Constructor<?> ctor = bdtConstructors.get(key + parent.toString());
             if (ctor != null)
             {
                return ctor;
             }

             parent = parent.getSuperclass();
          }

          // look for the interfaces
          Set<Class<?>> ifaces = Collections.newSetFromMap(new IdentityHashMap<>());
          collectInterfaces(cls, ifaces);
          for (Class<?> ifc : ifaces)
          {
             Constructor<?> ctor = bdtConstructors.get(key + ifc.toString());
             if (ctor != null)
             {
                return ctor;
             }
          }

          return null;
       }

       /**
        * Scroll given array so element with specified index will be first in
        * array. All elements before it will be placed at the end of the array.
        * <p>
        * NOTE: Scrolling is performed "in place", so input array gets updated!
        *
        * @param    <T>
        * @param    source
        *           Source array to scroll.
        * @param    first
        *           The index of element which will be first.
        *
        * @return   reference to the source array.
        */
       public static<T> T[] scroll(T[] source, int first)
       {
           if (first <= 0 || first >= source.length - 1)
               return source;

           T[] tmp1 = Arrays.copyOfRange(source, 0, first);
           T[] tmp2 = Arrays.copyOfRange(source, first, source.length);

           for (int i = 0; i < source.length; i++)
           {
               source[i] = (i < (source.length - first)) ? tmp2[i] :
                                                           tmp1[i - (source.length - first)];
           }

           return source;
       }

       /**
        * Convert the array of wrapper objects into the corresponding array of
        * primitive types.
        *
        * @param    array
        *           The array of wrapper objects.  Must not be <code>null</code>.
        *
        * @return   The corresponding primitive array.
        */
       public static int[] objectToPrimitive(Integer[] array)
       {
          int[] out = new int[array.length];

          for (int i = 0; i < array.length; i++)
          {
             out[i] = array[i].intValue();
          }

          return out;
       }

       /**
        * Convert the array of primitives into the corresponding array of wrapper
        * objects.
        *
        * @param    array
        *           The array of primitive types.  Must not be <code>null</code>.
        *
        * @return   The corresponding wrapper object array.
        */
       public static Integer[] primitiveToObject(int[] array)
       {
          Integer[] out = new Integer[array.length];

          for (int i = 0; i < array.length; i++)
          {
             out[i] = Integer.valueOf(array[i]);
          }

          return out;
       }

       /**
        * Convert a collection of wrapper objects into the corresponding array of
        * primitive types.
        *
        * @param    list
        *           The collection of wrapper objects. Must not be
        *           <code>null</code>.
        *
        * @return   The corresponding primitive array.
        */
       public static int[] integerCollectionToPrimitive(Collection<Integer> list)
       {
          return objectToPrimitive(list.toArray(new Integer[list.size()]));
       }

       /**
        * Convert a map of wrapper objects into the corresponding array of
        * primitive types.
        *
        * @param    map
        *           The map of wrapper objects. Must not be
        *           <code>null</code>.
        *
        * @return   The corresponding primitive arrays. Array on index 0 will hold
        *           the keys and array on index 1 will hold the values.
        */
       public static int[][] integerMapToPrimitive(Map<Integer, Integer> map)
       {
          int[][] res = new int[2][map.size()];
          int idx = 0;
          for (int key : map.keySet())
          {
             res[0][idx] = key;
             res[1][idx] = map.get(key);
             idx++;
          }
          return res;
       }

       /**
        * Convert an array of primitive types into a list of the corresponding
        * wrapper objects.
        *
        * @param    array
        *           The primitive array.  Must not be <code>null</code>.
        *
        * @return   The list of wrapper objects.
        */
       public static List<Integer> primitiveToIntegerList(int[] array)
       {
          return Arrays.asList(primitiveToObject(array));
       }

       /**
        * Check if a specific network end point can be connected from the local host.
        *
        * @param    host
        *           The given resolved host
        * @param    port
        *           The target port number on the given host to check for availability
        * @param    timeout
        *           The connection timeout in milliseconds
        *
        * @return   True if the port is listened on the remote host, otherwise false
        *
        * @throws   SocketTimeoutException
        *           It happens if timeout expires before connecting test completed
        */
       public static boolean isEndPointAvailable(InetAddress host, int port, int timeout)
       throws SocketTimeoutException
       {
          try(java.net.Socket sock = new java.net.Socket())
          {
             sock.setReuseAddress(true);
             sock.setSoLinger(false, 0);
             sock.setTcpNoDelay(true);
             sock.connect(new InetSocketAddress(host, port), timeout);
             return true;
          }
          catch(SocketTimeoutException ex0)
          {
             throw ex0;
          }
          catch (IOException ex1)
          {
             LOG.log(Level.WARNING, "not available host:port %s:%d.", host, port);
          }

          return false;
       }

       /**
        * Test if a specific network host name or IP address represents a network interface.
        *
        * @param    host
        *           The target network host name or IP address to check
        *
        * @return   True if the host represents a network interface, otherwise false
        */
       public static boolean isValidHostNameOrIpAddress(String host)
       {
          try
          {
             InetAddress addr = InetAddress.getByName(host);
             if (addr.isLoopbackAddress() || addr.isAnyLocalAddress())
             {
                return false;
             }

             return NetworkInterface.getByInetAddress(addr) != null;
          }
          catch(Throwable e)
          {
             return false;
          }
       }

       /**
        * Convert the array of wrapper objects into the corresponding array of
        * primitive types.
        *
        * @param    array
        *           The array of wrapper objects.  Must not be <code>null</code>.
        *
        * @return   The corresponding primitive array.
        */
       public static long[] objectToPrimitive(Long[] array)
       {
          long[] out = new long[array.length];

          for (int i = 0; i < array.length; i++)
          {
             out[i] = array[i].longValue();
          }

          return out;
       }

       /**
        * Convert the array of primitives into the corresponding array of wrapper
        * objects.
        *
        * @param    array
        *           The array of primitive types.  Must not be <code>null</code>.
        *
        * @return   The corresponding wrapper object array.
        */
       public static Long[] primitiveToObject(long[] array)
       {
          Long[] out = new Long[array.length];

          for (int i = 0; i < array.length; i++)
          {
             out[i] = Long.valueOf(array[i]);
          }

          return out;
       }

       /**
        * Convert a collection of wrapper objects into the corresponding array of
        * primitive types.
        *
        * @param    list
        *           The collection of wrapper objects. Must not be
        *           <code>null</code>.
        *
        * @return   The corresponding primitive array.
        */
       public static long[] longCollectionToPrimitive(Collection<Long> list)
       {
          return objectToPrimitive(list.toArray(new Long[list.size()]));
       }

       /**
        * Convert an array of primitive types into a list of the corresponding
        * wrapper objects.
        *
        * @param    array
        *           The primitive array.  Must not be <code>null</code>.
        *
        * @return   The list of wrapper objects.
        */
       public static List<Long> primitiveToLongList(long[] array)
       {
          return Arrays.asList(primitiveToObject(array));
       }

       /**
        * Load and return a <code>Properties</code> object from a properties
        * file specified by <code>filename</code>.
        *
        * @param   filename
        *          Name of the properties file.
        *
        * @return  Properties object containing the data read from
        *          <code>filename</code>.
        *
        * @throws  IOException
        *          if any error occurs reading the file.
        */
       public static Properties loadProperties(String filename)
       throws IOException
       {
          Properties props = new Properties();
          FileInputStream fis = null;
          BufferedInputStream bis = null;
          try
          {
             // Read properties from file.
             fis = new FileInputStream(filename);
             bis = new BufferedInputStream(fis);
             props.load(bis);
          }
          finally
          {
             // Clean up streams, regardless of success.
             if (bis != null)
             {
                bis.close();
             }
             else if (fis != null)
             {
                fis.close();
             }
          }

          return props;
       }

       /**
        * Retrieve the property with the specified <code>key</code> from within
        * the specified properties. Assume <code>key</code> represents a required
        * property; an exception is thrown if it is not present.
        *
        * @param   props
        *          Properties object from which to retrieve required property.
        * @param   key
        *          Key to the required property.
        *
        * @return  property value.
        *
        * @throws  MissingDataException
        *          if the required property is not present.
        */
       public static String getRequiredProperty(Properties props, String key)
       throws MissingDataException
       {
          String value = props.getProperty(key);
          if (value == null)
          {
             throw new MissingDataException("'" + key + "' is a required configuration property");
          }

          return value;
       }

       /**
        * Displays a prompt on <code>stdout</code> and returns the input entered
        * by the user on <code>stdin</code>.  The first new line encountered
        * will end the input reading loop and the text will not include this
        * new line character.
        *
        * @param    txt
        *           The text message to display.
        *
        * @return   The text that was interactively entered or <code>null</code>
        *           if there was no input provided.
        *
        * @throws IOException
        */
       public static char[] prompt(String txt)
       throws IOException
       {
          System.out.print(txt);
          System.out.flush();

          StringBuilder sb = new StringBuilder();

          int ch;

          readLoop:
          while (true)
          {
             switch (ch = System.in.read())
             {
                case -1:
                case '\n':
                   break readLoop;

                case '\f':
                case '\t':
                case '\r':
                   continue readLoop;

                default:
                   sb.append((char) ch);
                   break;
             }
          }

          if (sb.length() == 0)
          {
             return null;
          }

          return sb.toString().toCharArray();
       }

       /**
        * Determine if the given throwable or one of its causes matches one of the throwable
        * types given as criteria.
        *
        * @param    thr
        *           Throwable from which to examine cause chain.
        * @param    criteria
        *           List of throwables to check for.
        *
        * @return   <code>true</code> if any of the criteria throwables matches the given
        *           throwable or any throwable in the cause chain.
        */
       public static boolean causeChainContains(Throwable thr, Class[] criteria)
       {
          Throwable next = thr;
          do
          {
             for (int i = 0; i < criteria.length; i++)
             {
                if (criteria[i].isInstance(next))
                {
                   return true;
                }
             }

             next = next.getCause();
          }
          while (next != null);

          return false;
       }

       /**
        * Reports the default character set name. Since the Progress default
        * charset is ISO-8859-1, when the JVM reports that UTF-8 is the default,
        * this method will return the string "ISO-8859-1" instead.
        * <p>
        * The assumption (possibly not always correct) is that UTF-8 is not a
        * valid Progress charset and instead, to provide a proper default, we
        * must override with a compatible charset. Note that if the default
        * charset name is not UTF-8, it is assumed that the charset has been
        * overridden already (at JVM startup).  In that case, the default is
        * returned unchanged.
        *
        * @return   A default charset name.
        */
       public static String getCharsetName()
       {
          return getCharsetOverride();
       }

       /**
        * Overrides the default character set by the Progress default charset ISO-8859-1
        * if UTF-8 is the default JVM charset, otherwise returns the JVM default charset.
        * <p>
        * If the 4GL environment is actually configured for UTF-8, then this approach won't work.
        * If the default codepage name for the JVM is not UTF-8, it is assumed that the codepage
        * has been overriden already (at JVM startup).  In that case, the value reported by the JVM
        * is returned unchanged.
        *
        * @return   The JVM default charset if no override is required.
        */
       public static String getCharsetOverride()
       {
          Charset def  = Charset.defaultCharset();
          return getCharsetOverride(def.name());
       }

       /**
        * Overrides the given UTF-8 charset by the Progress default charset ISO-8859-1, otherwise
        * returns the given charset unchanged.
        *
        * @param    charsetName
        *           Charset name to override
        *
        * @return   The given charset name  if no override is required.
        */
       public static String getCharsetOverride(String charsetName)
       {
          String  cset;

          // this test may not be complete, but it handles the most common
          // case where the extended ASCII range will be "munged" by an
          // improper byte to char conversion
          if ("UTF-8".equalsIgnoreCase(charsetName))
          {
             // this gets called too much to allow it to log since we don't want it cluttering
             // client or conversion logs
             // token = UnimplementedFeature.todo("This code doesn't properly handle UTF-8.", token);

             // override with a safe default (matches the Progress default)
             cset = "ISO-8859-1";
          }
          else
          {
             cset = charsetName;
          }

          return cset;
       }

       /**
        * Convert the given byte to a Unicode character using the given character
        * set (or the default charset if the given charset name is
        * <code>null</code>).
        * <p>
        * The normal ASCII range can be converted to a Unicode character by a
        * simple widening conversion from byte to char. But the extended ASCII
        * range (128 through 255 inclusive) will translate to character values
        * that cannot be achieved via a widening conversion.  Instead the
        * proper character set must be used to get the right result.
        *
        * @param    b
        *           The byte to convert.
        * @param    cset
        *           The charset name to use for conversion or <code>null</code>
        *           if the default charset should be used.
        *
        * @return   The Unicode character that represents the given byte in the
        *           specified character set. In case of any error, -1 will be
        *           returned.
        */
       public static char toChar(byte b, String cset)
       {
          String txt = null;
          char   ch  = (char) -1;

          try
          {
             if (cset == null)
             {
                // use the default character set to translate this byte
                // into a character
                txt = new String(new byte[] {b});
             }
             else
             {
                // use a specific character set to convert rather than the
                // default
                txt = new String(new byte[] {b}, cset);
             }

             ch = txt.charAt(0);
          }

          catch (UnsupportedEncodingException uee)
          {
             // nothing to do
          }

          return ch;
       }

       /**
        * Convert the given byte to a Unicode character using the given character
        * set (or the default charset if the given charset name is
        * <code>null</code>).
        * <p>
        * <b>Only the least significant byte will be converted, all other data is
        * dropped at this time.</b>
        * <p>
        * The normal ASCII range can be converted to a Unicode character by a
        * simple widening conversion from byte to char. But the extended ASCII
        * range (128 through 255 inclusive) will translate to character values
        * that cannot be achieved via a widening conversion.  Instead the
        * proper character set must be used to get the right result.
        *
        * @param    keyCode
        *           The number to convert. Must be between 0 and 255 inclusive.
        * @param    cset
        *           The charset name to use for conversion or <code>null</code>
        *           if the default charset should be used.
        *
        * @return   The Unicode character that represents the given byte in the
        *           specified character set. In case of any error, -1 will be
        *           returned.
        */
       public static char toChar(int keyCode, String cset)
       {
          try
          {
             int codepoint = getCodepoint(keyCode, cset);
             if (codepoint == -1)
             {
                codepoint = keyCode;
             }
             return toChar(codepoint);
          }
          catch (IllegalArgumentException ex)
          {
             // no-op
          }

          return (char) -1;
       }

       /**
        * Convert the unicode code point to character.
        *
        * @param    codepoint
        *           The unicode code point.
        *
        * @return   The Unicode character that represents the given code point
        */
       public static char toChar(int codepoint)
       {
          try
          {
             if (codepoint != -1)
             {
                char[] chars = Character.toChars(codepoint);

                if (chars.length == 1)
                {
                   return chars[0];
                }
             }
          }
          catch (IllegalArgumentException ex)
          {
             // no-op
          }

          return (char) -1;
       }

       /**
        * Convert the given Unicode character to an integer value using the given
        * character set (or the default charset if the given charset name is
        * <code>null</code>).
        * <p>
        * The normal ASCII range can be converted from a Unicode character by a
        * simple narrowing conversion from char to byte. But the Unicode
        * characters representing the extended ASCII range (128 through 255
        * inclusive) are not represented in Unicode via a single byte. So a
        * translation from character values to a number cannot be achieved via a
        * narrowing conversion.  Instead the proper character set must be used to
        * get the right result.
        *
        * @param    ch
        *           The Unicode character to convert.
        * @param    cset
        *           The charset name to use for conversion or <code>null</code>
        *           if the default charset should be used.
        *
        * @return   The integer value that represents the given the Unicode
        *           character in the specified character set or -1 on any error.
        */
       public static int toInt(char ch, String cset)
       {
          int i = -1;

          try
          {
             String encoded = new String(new char[] {ch});
             byte[] b = (cset == null) ? encoded.getBytes() : encoded.getBytes(cset);
             i = b[0] & 0x000000FF;
          }

          catch (UnsupportedEncodingException uee)
          {
             // let -1 be returned
          }

          return i;
       }

       /**
        * Convert a unicode codepoint into codepoint according to the provided charset.
        *
        * @param    codepoint
        *           The given unicode codepoint
        * @param    cset
        *           The target charset name
        *
        * @return   The integer code representing the given unicode codepoint in the target charset.
        *
        * @throws   CharacterCodingException
        *           If the given unicode codepoint cannot be represented by the target charset
        * @throws   UnsupportedCharsetException
        *           If the target charset is not supported.
        */
       public static int convertCodepoint(int codepoint, String cset)
       throws CharacterCodingException, UnsupportedCharsetException
       {
          char[] keyChars = Character.toChars(codepoint);
          Charset charSet = Charset.forName(cset);
          CharsetEncoder encoder = charSet.newEncoder();
          encoder.onMalformedInput(CodingErrorAction.REPORT);
          encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
          ByteBuffer byteBuffer = encoder.encode(CharBuffer.wrap(keyChars));
          byte[] keyBytes = byteBuffer.array();
          int len = keyBytes.length;
          int len1 = len - 1;
          for(int i = len - 1; i >= 0; i--)
          {
             if (keyBytes[i] != 0)
             {
                len1 = i;
                break;
             }
          }

          int ret = 0;

          for (int i = 0; i <= len1; i++)
          {
             int bt = Byte.toUnsignedInt(keyBytes[i]);

             //System.out.println(Integer.toString(bt, 16));

             ret = ret | bt;

             if (i < len1)
             {
                ret = ret << 8;
             }
          }

          return ret;
       }

       /**
        * Get the unicode codepoint for the given key code and char set.
        *
        * @param    keyCode
        *           The given key code
        * @param    cset
        *           The given char set.
        *
        * @return   The unicode codepoint if keycode is a valid key code in the given char set, otherwise -1.
        */
       public static int getCodepoint(int keyCode, String cset)
       {
          ByteBuffer bf = ByteBuffer.allocate(4);
          bf.putInt(keyCode);
          bf.flip();
          try
          {
             for (int i = 0; i < 4; i++)
             {
                if (bf.get(i) != 0)
                {
                   bf.position(i);
                   break;
                }
             }

             byte[] buf = new byte[bf.limit() - bf.position()];

             for (int i = bf.position(), j = 0; i < bf.limit(); i++, j++)
             {
                buf[j] = bf.get(i);
             }

             Charset charSet = Charset.forName(cset);
             CharsetDecoder decoder = charSet.newDecoder();
             decoder.onMalformedInput(CodingErrorAction.REPORT);
             decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
             CharBuffer charBuffer = decoder.decode(ByteBuffer.wrap(buf));

             String key = new String(charBuffer.array());
             if (key.length() == 1)
             {
                return Character.codePointAt(key, 0);
             }
          }
          catch (IllegalArgumentException | CharacterCodingException e)
          {
          }

          return -1;
       }

       /**
        * Creates all subdirectories needed to provide a valid path for the
        * target filename that is passed in.  It is expected that there will
        * always be a path included in this filename such that making
        * directories makes sense.
        *
        * @param    tar
        *           Target filename for which paths must be created.
        *
        * @return   <code>true</code> if the full necessary path already
        *           exists or if it has been created, <code>false</code>
        *           otherwise.
        */
       public static boolean createPathForTarget(String tar)
       {
          String path = tar.substring(0, tar.lastIndexOf(File.separatorChar));
          File   file = new File(path);

          // anything to do?
          if (file.exists() && file.isDirectory())
             return true;

          try
          {
             // best effort to create the directories including all needed parent dirs
             file.mkdirs();
          }
          catch (Exception exc)
          {
             // TODO: add logging
             // some failure, probably a security violation
             return false;
          }

          // it better be there now...
          if (file.exists() && file.isDirectory())
             return true;

          // some other unexpected problem!
          return false;
       }

       /**
        * Find the specified file in the file system using the specified search
        * paths and return it. Handles absolute and relative paths.
        *
        * @param   filename
        *          Name of the source file to find; if this is not an absolute
        *          path, it should be relative to one of the paths in the
        *          <code>searchPaths</code> array.
        * @param   paths
        *          Array of absolute paths which are prepended to a relative
        *          <code>filename</code>.
        *
        * @return  The file if it was found.
        *
        * @throws  FileNotFoundException
        *          if a file named <code>filename</code> does not exist in the
        *          specified search paths (or at the absolute path provided).
        */
       public static File findFileInPath(String filename, String[] paths)
       throws FileNotFoundException
       {
          File file = new File(filename);

          if (file.isAbsolute())
          {
             // Return file if it is absolute and exists.
             if (file.exists())
             {
                return file;
             }
          }
          else
          {
             // Use search paths to resolve relative references.
             int len = paths.length;
             for (int i = 0; i < len; i++)
             {
                file = new File(paths[i], filename);
                if (file.exists())
                {
                   return file;
                }
             }
          }

          // Reassemble path for error message.
          StringBuilder buf = new StringBuilder();
          String sep = File.pathSeparator;
          int len = paths.length;
          for (int i = 0; i < len; i++)
          {
             buf.append(paths[i])
                .append(sep);
          }

          throw new FileNotFoundException("File '" + filename + "' not found in path: " + buf);
       }

       /**
        * Calculates a resource path to the target page within its application class path.
        *
        * @param    pageContext
        *           The page root path
        * @param    location
        *           The package where the target resource is located.
        * @param    pageFile
        *           The resource name with its extension.
        *
        * @return   The string path to be found within its application class path.
        */
       public static String calculateResourcePath(String   pageContext,
                                                  Package  location,
                                                  String   pageFile)
       {
          StringBuilder sb = new StringBuilder(pageContext);

          String packageName = location.getName();

          sb.append(packageName.replace('.', '/'));

          if (!pageFile.startsWith("/"))
          {
             sb.append("/");
          }

          return sb.append(pageFile).toString();
       }

       /**
        * Canonicalize the given file system path as described by
        * <code>java.io.File.getCanonicalPath()</code>.  In addition, normalize
        * the use of the file separator by replacing all instances of
        * <code>oldSeparator</code> with <code>newSeparator</code>.
        *
        * @param   path
        *          A relative or absolute path to be canonicalized.
        * @param   oldSeparator
        *          File separator to be replaced.
        * @param   newSeparator
        *          New file separator.
        *
        * @return  Canonicalized path containing normalized file separators.
        *
        * @throws  IOException
        *          If there is a file system while canonicalizing the path.
        */
       public static String canonicalizePath(String path, String oldSeparator, String newSeparator)
       throws IOException
       {
          String res = null;
          try
          {
             res = new File(path).getCanonicalPath();
          }
          catch (IOException e)
          {
             // On windows trying to access some path could throw exceptions like these:
             // TODO: identify correct fix and remove this line
             LOG.warning("", e);

             res = path; // consider path as already canonical (or return an invalid value ?)
          }

          res = replaceSeparator(res, oldSeparator, newSeparator);

          return res;
       }

       /**
        * Returns URI based on the file schema and the given path.
        *
        * @param    path
        *           The given path
        *
        * @return   The URI based on the file path
        * @throws   URISyntaxException
        *           If the given path cannot be transformed to URI.
        */
       public static URI toURI(String path)
       throws URISyntaxException
       {
          return new URI("file", null, path, null, null);
       }

       /**
        * Replace all instances of {@code oldSeparator} with {@code newSeparator} in the given path.
        *
        * @param   path
        *          A file path.
        * @param   oldSeparator
        *          File separator to be replaced.
        * @param   newSeparator
        *          New file separator.
        *
        * @return  Path containing normalized file separators.
        */
       public static String replaceSeparator(String path, String oldSeparator, String newSeparator)
       {
          if (!oldSeparator.equals(newSeparator))
          {
             if ("\\".equals(oldSeparator))
             {
                oldSeparator = "\\\\";
             }
             if ("\\".equals(newSeparator))
             {
                newSeparator = "\\\\";
             }
             path = path.replaceAll(oldSeparator, newSeparator);
          }

          return path;
       }

       /**
        * Determines if a both source and target exist as files and if the target
        * has a later timestamp.  It does not consider contents or size in this
        * determination.
        *
        * @param    source
        *           Source filename.
        * @param    target
        *           Target filename.
        *
        * @return   <code>true</code> if the both files exist and the target is
        *           newer, otherwise <code>false</code> (includes failures).
        */
       public static boolean isLaterVersion(String source, String target)
       {
          File src = new File(source);
          File tar = new File(target);

          // make sure the source file is OK
          boolean srcOK = src.exists() && src.isFile();

          if (!srcOK)
             return false;

          // make sure the target file is not there or if it is there that
          // it is older than the source
          if (tar.exists())
          {
             if (tar.isFile())
             {
                // returns true if the source is older, false if the target is
                // older
                return src.lastModified() < tar.lastModified();
             }
          }

          return false;
       }

       /**
        * Creates the directory tree to the target if needed and then copies
        * the source to the target if needed.  The copy is byte for byte, no
        * modifications of the file will occur.
        *
        * @param    src
        *           Source filename.
        * @param    tar
        *           Target filename.
        *
        * @return   <code>true</code> if the file was copied successfully OR
        *           if the file didn't need to be copied, <code>false</code>
        *           on any failure.
        */
       public static boolean simpleCopy(String src, String tar)
       {
          // try to create the directory
          if (!createPathForTarget(tar))
          {
             // dir creation failed
             return false;
          }

          return copyFileIfNeeded(src, tar, 0);
       }

       /**
        * Conditionally copies a file, byte for byte (no changes) to a new file,
        * if and only if the source is an accessible file and the target doesn't
        * exist or the target exists as a file but is older than the source or
        * has a different size.
        *
        * @param    source
        *           Source filename.
        * @param    target
        *           Target filename.
        * @param    bsize
        *           Buffer size or if 0 the buffer size will default to the file
        *           size or 64Kb whichever is less.
        *
        * @return   <code>true</code> if the file was copied successfully OR if
        *           the file didn't need to be copied, <code>false</code> on any
        *           failure.
        */
       public static boolean copyFileIfNeeded(String source, String target, int bsize)
       {
          File src = new File(source);
          File tar = new File(target);

          // make sure the source file is OK
          boolean srcOK = src.exists() && src.isFile();

          if (!srcOK)
             return false;

          boolean tarOK = true;

          // make sure the target file is not there or if it is there that
          // it is older than the source or it is not the same size, before
          // trying to copy
          if (tar.exists())
          {
             if (tar.isFile())
             {
                boolean oldSrc = src.lastModified() > tar.lastModified();
                long    diff   = src.length() - tar.length();
                tarOK = oldSrc || (diff != 0);
             }
             else
             {
                return false;
             }
          }

          boolean result = false;

          // already checked src, now check target
          if (tarOK)
          {
             // best efforts attempt to copy
             result = copyFile(source, target, 0, false);
          }

          return result;
       }

       /**
        * Handles the byte for byte copying of a single source file to a target
        * file, using a buffer size of the caller's choice. The resulting target
        * file will be a newly created file (date/timestamps will be new).
        *
        * @param    source
        *           Source filename.
        * @param    target
        *           Target filename.
        * @param    bsize
        *           Buffer size or if 0 the buffer size will default to the file
        *           size or 64Kb whichever is less.
        * @param    append
        *           <code>true</code> if the output file should be opened in
                    append mode.
        *
        * @return   <code>true</code> if the file was copied successfully,
        *           <code>false</code> on any failure.
        */
       public static boolean copyFile(String  source, String target, int bsize, boolean append)
       {
          File in  = new File(source);
          long len = in.length();

          // if an invalid buffer size is set, the lesser of 64Kb or the file
          // length is used
          if (bsize < 1)
          {
             bsize = 64*1024;

             if (len < bsize)
                bsize = (int) len;
          }

          try
          {
             FileInputStream  src = new FileInputStream(in);
             FileOutputStream tar = new FileOutputStream(target, append);

             int off = 0;

             byte[] buf = new byte[bsize];

             while (len > bsize)
             {
                src.read(buf, off, bsize);
                tar.write(buf, 0, bsize);
                len -= bsize;
             }

             src.read(buf, off, (int)len);
             tar.write(buf, 0, (int)len);

             src.close();
             tar.close();
          }
          catch (Exception exc)
          {
             return false;
          }

          return true;
       }

       /**
        * Supply a list of files which case-insensitively match the given file
        * name.  On a case-insensitive file system, this list will contain no more
        * than one match.  However, on a case-sensitive file system, the list may
        * contain more matches.
        *
        * @param   path
        *          The file name to be matched.  It will be canonicalized before
        *          the search is conducted.
        * @param   separator
        *          Legacy system file separator which will be replaced with the
        *          target system's file separator (in the result list).
        *
        * @return  A list containing 0 or more files.  This method will never
        *          return <code>null</code>.
        *
        * @throws  IOException
        *          if there is any error canonicalizing the <code>path</code>.
        */
       public static List<File> getCaseInsensitiveFilenameMatches(String path, String separator)
       throws IOException
       {
          // canonicalize path and ensure file separators are appropriate for the
          // current platform's file system
          String canonicalPath = canonicalizePath(path, separator, File.separator);

          return getCaseInsensitiveFilenameMatches(canonicalPath);
       }

       /**
        * Supply a list of files which case-insensitively match the given file
        * name.  On a case-insensitive file system, this list will contain no more
        * than one match.  However, on a case-sensitive file system, the list may
        * contain more matches.
        *
        * @param   path
        *          The file name to be matched.  It must already be canonicalized
        *          (see {@link #canonicalizePath}) and with all file separators
        *          converted to the current system's file separator character.
        *
        * @return  A list containing 0 or more files.  This method will never
        *          return <code>null</code>.
        *
        * @throws  IOException
        *          if there is any error canonicalizing the <code>path</code>.
        */
       public static List<File> getCaseInsensitiveFilenameMatches(String path)
       throws IOException
       {
          List<File> list = new ArrayList<>(1);
          matchPathCaseInsensitively(null, path, list);

          return list;
       }

       /**
        * Get a string which uniquely describes the current context.  This is
        * intended primarily for debugging or logging purposes.
        *
        * @return  Description of context.
        */
       public static String describeContext()
       {
          SecurityManager security = SecurityManager.getInstance();
          if (security == null || !security.isServer())
          {
             return Thread.currentThread().getName();
          }

          StringBuilder buf = new StringBuilder();

          // Session ID.
          buf.append(String.format("%08X", security.sessionSm.getSessionId()));
          buf.append(":");

          // Thread ID.
          Object tid = security.getThreadId();
          if (tid != null)
          {
             buf.append(String.format("%08X", tid));
          }
          else
          {
             buf.append("????????");
          }
          buf.append(":");

          // User ID.
          buf.append(security.getUserId());

          return buf.toString();
       }

       /**
        * Invoke the specified {@link Method} using ReflectASM instead of standard reflection.
        * <p>
        * ReflectASM related data is cached in {@link #METHOD_DATA}, thread-local, to avoid the concurrent access.
        * More, the maps are identity-keyed, for fast access.
        *
        * @param    method
        *           The method to execute.
        * @param    instance
        *           The instance on which the call will be made; can be <code>null</code> for static methods.
        * @param    args
        *           The method's arguments.
        *
        * @return   The method's return value.
        *
        * @throws   IllegalAccessException
        * @throws   IllegalArgumentException
        * @throws   InvocationTargetException
        */
       public static Object invoke(Method method, Object instance, Object... args)
       throws IllegalAccessException,
              IllegalArgumentException,
              InvocationTargetException
       {
          if (instance == null && !Modifier.isStatic(method.getModifiers()))
          {
             throw new NullPointerException("NULL instance used when invoking method " + method.toString() +
                                            " in " + method.getDeclaringClass());
          }

          Class<?> cls = method.getDeclaringClass();
          MethodData mdata = METHOD_DATA.get(cls);
          if (mdata == null)
          {
             METHOD_DATA.put(cls, mdata = new MethodData(cls));
          }
          Integer accessIdx = mdata.methodAccessIdx.get(method);

          return mdata.access.invoke(instance, accessIdx, args);
          // return method.invoke(instance, args);
       }

       /**
        * Executes the given entry point using reflection, instantiating
        * the target class first, if necessary.
        * Catches the exceptions and rethrows those of them that represent
        * Progress conditions.
        *
        * @param    cl
        *           The class to be instantiated. Must be public. Must have a
        *           public, default constructor, unless <code>entry</code> is a
        *           static method.
        * @param    entry
        *           The method to be executed. Must exist within <code>cl</code>.
        *           May be an instance method or static method;  must be
        *           publicly accessible.
        * @param    args
        *           If the method's signature contains parameters, this is a
        *           variable argument list containing those parameters.
        *
        * @return   The result of the invocation.  If the signature of the method
        *           specifies a <code>void</code> return then this will return
        *           <code>null</code>.  Note that it is a limitation of the
        *           design of Java method signatures such that we do not
        *           distinguish between a method with a <code>void</code> return
        *           and a method that has a real return value which is set to
        *           <code>null</code>.
        * @throws NullPointerException
        * @throws IllegalAccessException
        * @throws IllegalArgumentException
        * @throws InvocationTargetException
        * @throws InstantiationException
        */
       public static Object invoke(Class<?> cl, Method entry, Object... args)
       throws NullPointerException,
              IllegalAccessException,
              IllegalArgumentException,
              ReflectiveOperationException,
              InstantiationException
       {
          // simple test for a quick out
          if (entry == null)
          {
             throw new NullPointerException("Specified entry point is null!");
          }

          Object result = null;

          try
          {
             Object instance = null;

             // instantiate the class (for non-static method)
             if ((entry.getModifiers() & Modifier.STATIC) == 0)
             {
                String pname = SourceNameMapper.getLegacySourceName(cl.getName());
                ProcedureManager.setInstantingExternalProgram(pname);
                ProcedureManager.setInstantingExternalProgramClass(cl);
                try
                {
                   instance = cl.getDeclaredConstructor().newInstance();
                }
                catch (ErrorConditionException ece)
                {
                   String msg = ece.getMessage();
                   int num = ece.getProgressErrorCode();
                   ErrorManager.recordOrThrowError(num, msg, false);
                }
                finally
                {
                   ProcedureManager.setInstantingExternalProgram(null);
                   ProcedureManager.setInstantingExternalProgramClass(null);
                }
             }

             // invoke the method
             result = entry.invoke(instance, args);
          }
          catch (InvocationTargetException exc)
          {
             // if the exception thrown was a P2J runtime type, we
             // must handle it normally, so extract the root cause
             // exception and rethrow it
             Throwable cause = exc.getCause();

             if (cause != null)
             {
                if (cause instanceof ConditionException)
                {
                   throw (ConditionException) cause;
                }
                if (cause instanceof RetryUnwindException)
                {
                   throw (RetryUnwindException) cause;
                }

                // exception is a runtime problem that we don't handle here
                throw exc;
             }
          }
          return result;
       }

       /**
        * Verify that a directory node at a particular, required directory path
        * exists, and that the class of the node found at that path is that which
        * is expected.
        *
        * @param   ds
        *          Directory service.
        * @param   id
        *          ID of node to verify.
        * @param   expected
        *          Expected class of node.
        *
        * @throws  RuntimeException
        *          if directory node does not exist or is of the incorrect class.
        */
       public static void verifyDirectoryNode(DirectoryService ds, String id, String expected)
       throws RuntimeException
       {
          String nodeClass = ds.getNodeClass(id);

          if (nodeClass == null)
          {
             throw new RuntimeException("Directory entry missing:  " + id);
          }

          if (!nodeClass.equals(expected))
          {
             throw new RuntimeException("Directory entry is incorrect type:  " + id);
          }
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static int getDirectoryNodeInt(DirectoryService ds, String nodeId, int initial)
       {
          return getDirectoryNodeInt(ds, nodeId, initial, DirScope.ACCOUNTS, null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static double getDirectoryNodeDouble(DirectoryService ds, String nodeId, double initial)
       {
          return getDirectoryNodeDouble(ds, nodeId, initial, DirScope.ACCOUNTS, null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static char getDirectoryNodeChar(DirectoryService ds, String nodeId, char initial)
       {
          return getDirectoryNodeChar(ds, nodeId, initial, DirScope.ACCOUNTS, null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static String getDirectoryNodeString(DirectoryService ds, String nodeId, String initial)
       {
          return getDirectoryNodeString(ds, nodeId, initial, DirScope.ACCOUNTS, null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static boolean getDirectoryNodeBoolean(DirectoryService ds,
                                                     String           nodeId,
                                                     boolean          initial)
       {
          return getDirectoryNodeBoolean(ds, nodeId, initial, DirScope.ACCOUNTS, null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           <code>true</code> if account related search, otherwise server
        *           related search is assumed.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static int getDirectoryNodeInt(DirectoryService ds,
                                             String           nodeId,
                                             int              initial,
                                             boolean          scope)
       {
          return getDirectoryNodeInt(ds, nodeId, initial,
                                     (scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           <code>true</code> if account related search, otherwise server
        *           related search is assumed.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static double getDirectoryNodeDouble(DirectoryService ds,
                                                   String           nodeId,
                                                   double           initial,
                                                   boolean          scope)
       {
          return getDirectoryNodeDouble(ds, nodeId, initial,
                                        (scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           <code>true</code> if account related search, otherwise server
        *           related search is assumed.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static char getDirectoryNodeChar(DirectoryService ds,
                                               String           nodeId,
                                               char             initial,
                                               boolean          scope)
       {
          return getDirectoryNodeChar(ds, nodeId, initial,
                                      (scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           <code>true</code> if account related search, otherwise server
        *           related search is assumed.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static String getDirectoryNodeString(DirectoryService ds,
                                                   String           nodeId,
                                                   String           initial,
                                                   boolean          scope)
       {
          return getDirectoryNodeString(ds, nodeId, initial,
                                        (scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           <code>true</code> if account related search, otherwise server
        *           related search is assumed.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static boolean getDirectoryNodeBoolean(DirectoryService ds,
                                                     String           nodeId,
                                                     boolean          initial,
                                                     boolean          scope)
       {
          return getDirectoryNodeBoolean(ds, nodeId, initial,
                                         (scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
       }

       /**
        * Obtain the path to the node in the directory if it exists or return
        * <code>null</code> if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows paths to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * There are two search scopes: account relayed and server related.
        * Account related search is described below. The server related search
        * is a version of the search that is limited to the first two server
        * related levels.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    expected
        *           Expected class of node.  This will be checked with that
        *           found in the directory for consistency.
        * @param    scope
        *           <code>true</code> if account related search, otherwise server
        *           related search is assumed.
        *
        * @return   The path to the found node or <code>null</code> if for ANY
        *           reason the directory node cannot be found.
        */
       public static String findDirectoryNodePath(DirectoryService ds,
                                                  String           nodeId,
                                                  String           expected,
                                                  boolean          scope)
       {
          return findDirectoryNodePath(ds, nodeId, expected,
                                       (scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static int getDirectoryNodeInt(DirectoryService ds,
                                             String           nodeId,
                                             int              initial,
                                             DirScope         scope,
                                             String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeInteger(nodeId, attrName);
             }
          };

          Integer result = (Integer) getDirectoryNodeWorker(ds,
                                                            nodeId,
                                                            "integer",
                                                            helper,
                                                            scope,
                                                            accountIds);

          return (result == null) ? initial : result.intValue();
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static Integer getDirectoryNodeInt(DirectoryService ds,
                                                 String           nodeId,
                                                 Integer          initial,
                                                 DirScope         scope,
                                                 String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeInteger(nodeId, attrName);
             }
          };

          Integer result = (Integer) getDirectoryNodeWorker(ds,
                                                            nodeId,
                                                            "integer",
                                                            helper,
                                                            scope,
                                                            accountIds);

          return (result == null) ? initial : result;
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static double getDirectoryNodeDouble(DirectoryService ds,
                                                   String           nodeId,
                                                   double           initial,
                                                   DirScope         scope,
                                                   String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeDouble(nodeId, attrName);
             }
          };

          Double result = (Double) getDirectoryNodeWorker(ds,
                                                          nodeId,
                                                          "double",
                                                          helper,
                                                          scope,
                                                          accountIds);

          return (result == null) ? initial : result.doubleValue();
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static char getDirectoryNodeChar(DirectoryService ds,
                                               String           nodeId,
                                               char             initial,
                                               DirScope         scope,
                                               String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeString(nodeId, attrName);
             }
          };

          String result = (String) getDirectoryNodeWorker(ds,
                                                          nodeId,
                                                          "string",
                                                          helper,
                                                          scope,
                                                          accountIds);

          return (result != null && result.length() > 0) ? result.charAt(0) : initial;
       }

       /**
        * Fills the provided or new created map with key and values found at the node given by its
        * relative path. The search algorithm is defined by the scope value. If it is true, then
        * accounts search is executed, otherwise servers search is executed only. The key and value
        * pairs are defined by "entry" class following this example:
        * <code><br>
        *            &lt;node class="entry" name="entry1"&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="key" value="PAPERSIZE"/&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="value" value="letter"/&gt;<br>
        *            &lt;/node&gt;<br>
        *            &lt;node class="entry" name="entry2"&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="key" value="LANG"/&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="value" value="en_US"/&gt;<br>
        *            &lt;/node&gt;<br>
        * </code>
        *
        * @param    ds
        *           The directory service
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    map
        *           The provided map that can be null
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    caseInsensitiveKeys
        *           The true value indicates that all map keys are case insensitive and mapped to
        *           their upper cases.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The filled provided map or new created map filled with found values.
        */
       public static Map<String, String> getDirectoryNodeMap(DirectoryService    ds,
                                                             String              nodeId,
                                                             Map<String, String> map,
                                                             DirScope            scope,
                                                             boolean             caseInsensitiveKeys,
                                                             String[]            accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper(null)
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                String[] names = ds.enumerateNodes(nodeId);

                if (names == null)
                {
                   return map != null ? map : Collections.emptyMap();
                }

                Map<String, String> nodeMap;

                if (map != null)
                {
                   nodeMap = map;
                }
                else
                {
                   nodeMap = new LinkedHashMap<>();
                }

                for(String name : names)
                {
                   String entryNodeId = nodeId + "/" + name;
                   String key = ds.getNodeString(entryNodeId, "key");
                   String value = ds.getNodeString(entryNodeId, "value");
                   if (caseInsensitiveKeys)
                   {
                      key = key.toUpperCase(Locale.ROOT);
                   }
                   nodeMap.put(key, value);
                }

                return nodeMap;
             }
          };

          Map<String, String> result = (Map<String, String>) getDirectoryNodeWorker(ds,
                                                                                    nodeId,
                                                                                    "container",
                                                                                    helper,
                                                                                    scope,
                                                                                    accountIds);

          return (result == null) ? Collections.emptyMap() : result;
       }

       /**
        * Fills the provided or new created map with key and values found at the node given by its
        * relative path. The search algorithm is defined by the scope value. If it is true, then
        * accounts search is executed, otherwise servers search is executed only. The key and value
        * pairs are defined by "entry" class following this example:
        * <code><br>
        *            &lt;node class="entry" name="entry1"&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="key" value="PAPERSIZE"/&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="value" value="letter"/&gt;<br>
        *            &lt;/node&gt;<br>
        *            &lt;node class="entry" name="entry2"&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="key" value="LANG"/&gt;<br>
        * &nbsp;&nbsp; &lt;node-attribute name="value" value="en_US"/&gt;<br>
        *            &lt;/node&gt;<br>
        * </code>
        *
        * @param    ds
        *           The directory service
        * @param    nodeRelativePath
        *           The entries container relative path
        * @param    map
        *           The provided map that can be null
        * @param    scope
        *           The true value indicates accounts search, otherwise the server search is used only
        * @param    caseInsensitiveKeys
        *           The true value indicates that all map keys are case insensitive and mapped to
        *           their upper cases.
        *
        * @return   The filled provided map or new created map filled with found values.
        */
       public static Map<String, String> getDirectoryNodeMap(DirectoryService    ds,
                                                             String              nodeRelativePath,
                                                             Map<String, String> map,
                                                             boolean             scope,
                                                             boolean             caseInsensitiveKeys)
       {
          if (!ds.bind())
          {
             throw new RuntimeException("directory bind failed");
          }

          try
          {
             String nodePath = Utils.findDirectoryNodePath(ds,
                                                           nodeRelativePath,
                                                           "container",
                                                           scope);

             if (nodePath == null)
             {
                // not found
                return map != null ? map : Collections.emptyMap();
             }

             String[] names = ds.enumerateNodes(nodePath);

             if (names == null)
             {
                return map != null ? map : Collections.emptyMap();
             }

             Map<String, String> pathPrefixMap;

             if (map != null)
             {
                pathPrefixMap = map;
             }
             else
             {
                pathPrefixMap = new LinkedHashMap<>();
             }

             for(String name : names)
             {
                String nodeId = nodePath + "/" + name;
                String key = ds.getNodeString(nodeId, "key");
                String value = ds.getNodeString(nodeId, "value");
                if (caseInsensitiveKeys)
                {
                   key = key.toUpperCase(Locale.ROOT);
                }
                pathPrefixMap.put(key, value);
             }

             return pathPrefixMap;
          }
          finally
          {
             ds.unbind();
          }
       }

       /**
        *
        * @param    <TBean>
        *           The parameter type representing the class for a java bean object with all public fields.
        * @param    ds
        *           The directory service
        * @param    nodeRelativePath
        *           The entries container relative path
        * @param    list
        *           The list to fill, it can be null
        * @param    cls
        *           The class for objects being retrieved from the directory by this method.
        * @param    scope
        *           The true value indicates accounts search, otherwise the server search is used only
        * @param    attributes
        *           The true value indicates TBean object properties are serialized as node attributes,
        *           otherwise properties are given as nodes.
        *
        * @return   The list of target objects.
        *
        * @throws   Throwable
        *           If the target class has no default constructor or its fields are not accessible or
        *           the directory service is failed.
        */
       public static <TBean> List<TBean> getDirectoryNodeBeanList( DirectoryService ds,
                                                                   String nodeRelativePath,
                                                                   List<TBean> list,
                                                                   Class<TBean> cls,
                                                                   boolean scope,
                                                                   boolean attributes)
       throws Throwable
       {
          if (!ds.bind())
          {
             throw new RuntimeException("directory bind failed");
          }

          try
          {
             String nodePath = Utils.findDirectoryNodePath(ds, nodeRelativePath, "container", scope);

             if (nodePath == null)
             {
                // not found
                return list != null ? list : Collections.emptyList();
             }

             String[] names = ds.enumerateNodes(nodePath);

             if (names == null)
             {
                return list != null ? list : Collections.emptyList();
             }

             List<TBean> resultList;

             if (list != null)
             {
                resultList = list;
             }
             else
             {
                resultList = new LinkedList<>();
             }
             Field[] fields = cls.getFields();
             MethodHandles.Lookup lookup = MethodHandles.lookup();
             MethodType mt = MethodType.methodType(void.class);
             MethodHandle defCons = lookup.findConstructor(cls, mt);
             for (String name : names)
             {
                String nodeId = nodePath + "/" + name;
                Object obj = defCons.invoke();
                for (Field f : fields)
                {
                   MethodHandle mh = lookup.unreflectSetter(f);
                   Object value = null;
                   String nm = f.getName();
                   String fieldNodeId, attributeName;
                   if (attributes)
                   {
                      fieldNodeId = nodeId;
                      attributeName = nm;
                   }
                   else
                   {
                      fieldNodeId = nodeId + "/" + nm;
                      attributeName = "value";
                   }
                   if (f.getType().equals(String.class))
                   {
                      value = ds.getNodeString(fieldNodeId, attributeName);
                   }
                   else if (f.getType().equals(Integer.TYPE))
                   {
                      value = ds.getNodeInteger(fieldNodeId, attributeName);
                   }
                   else if (f.getType().equals(Boolean.TYPE))
                   {
                      value = ds.getNodeBoolean(fieldNodeId, attributeName);
                   }
                   if (value != null)
                   {
                      mh.invoke(obj, value);
                   }
                }
                resultList.add((TBean) obj);
             }

             return resultList;
          }
          finally
          {
             ds.unbind();
          }
       }

       /**
        * Enumerates the nodes found at the node given by its relative path. The search algorithm is defined by
        * the scope value. If it is true, then accounts search is executed, otherwise servers search is
        * executed only.
        *
        * @param    directoryService
        *           The directory service
        * @param    nodeRelativePath
        *           The entries container relative path
        * @param    scope
        *           The true value indicates accounts search, otherwise the server search is used only
        * @param    func
        *           A function called for every sub-node found at the path given by {@code nodeRelativePath}.
        *           The function is passed node name of every sub-node encountered. The enumeration continues
        *           until all the sub-nodes are enumerated or the function returns any other value than
        *           {@code true}.
        *
        * @return   {@code false} when the node given by {@code nodeRelativePath} is not found, {@code true}
        *           otherwise.
        */
       public static boolean enumDirectoryNodes(DirectoryService directoryService,
                                                String nodeRelativePath,
                                                boolean scope,
                                                Function<String, Boolean> func)
       {
          return doWithBoundDS(directoryService, true, ds ->
          {
             String nodePath = Utils.findDirectoryNodePath(ds,
                                                           nodeRelativePath,
                                                           "container",
                                                           scope);

             if (nodePath == null)
             {
                // not found
                return false;
             }

             String[] names = ds.enumerateNodes(nodePath);

             if (names == null)
             {
                return false;
             }

             for (String name : names)
             {
                Boolean res = func.apply(name);
                if (!Objects.equals(res, Boolean.TRUE))
                {
                   break;
                }
             }

             return true;
          });
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static String getDirectoryNodeString(DirectoryService ds,
                                                   String           nodeId,
                                                   String           initial,
                                                   DirScope         scope,
                                                   String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeString(nodeId, attrName);
             }
          };

          String result = (String) getDirectoryNodeWorker(ds,
                                                          nodeId,
                                                          "string",
                                                          helper,
                                                          scope,
                                                          accountIds);

          return (result == null) ? initial : result;
       }

       /**
        * Obtain the specified attribute values from the directory if it exists or return
        * an empty list if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * &lt;p&gt;
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * &lt;p&gt;
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * &lt;p&gt;
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * &lt;p&gt;
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * &lt;p&gt;
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * &lt;p&gt;
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * &lt;p&gt;
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           {@code null} then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static List<String> getDirectoryNodeStrings(DirectoryService ds,
                                                          String           nodeId,
                                                          DirScope         scope,
                                                          String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper(null)
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeStrings(nodeId, "values");
             }
          };

          String[] result = (String[]) getDirectoryNodeWorker(ds,
                                                              nodeId,
                                                              "strings",
                                                              helper,
                                                              scope,
                                                              accountIds);

          return (result == null) ? Collections.emptyList() : Arrays.asList(result);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static boolean getDirectoryNodeBoolean(DirectoryService ds,
                                                     String           nodeId,
                                                     boolean          initial,
                                                     DirScope         scope,
                                                     String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeBoolean(nodeId, attrName);
             }
          };

          Boolean result = (Boolean) getDirectoryNodeWorker(ds,
                                                            nodeId,
                                                            "boolean",
                                                            helper,
                                                            scope,
                                                            accountIds);

          return (result == null) ? initial : result.booleanValue();
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static Boolean getDirectoryNodeBoolean(DirectoryService ds,
                                                     String           nodeId,
                                                     Boolean          initial,
                                                     DirScope         scope,
                                                     String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeBoolean(nodeId, attrName);
             }
          };

          Boolean result = (Boolean) getDirectoryNodeWorker(ds,
                                                            nodeId,
                                                            "boolean",
                                                            helper,
                                                            scope,
                                                            accountIds);

          return (result == null) ? initial : result;
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return
        * the default value if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * No exceptions are thrown, the given initial value will always be safely
        * returned.
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    initial
        *           The default value to return if the directory backed value
        *           cannot be obtained for ANY reason.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or the given initial value if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static byte[] getDirectoryNodeByteArray(DirectoryService ds,
                                                      String           nodeId,
                                                      byte[]           initial,
                                                      DirScope         scope,
                                                      String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeByteArray(nodeId, attrName);
             }
          };

          byte[] result = (byte[]) getDirectoryNodeWorker(ds,
                                                          nodeId,
                                                          "bytes",
                                                          helper,
                                                          scope,
                                                          accountIds);

          return (result == null) ? initial : result;
       }

       /**
        * Enumerate nodes in the specified container from the directory if it exists or return
        * an empty list if the container directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * &lt;p&gt;
        * This method implements a search algorithm that allows values to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * &lt;p&gt;
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * &lt;p&gt;
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * &lt;p&gt;
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * &lt;p&gt;
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * &lt;p&gt;
        * No exceptions are thrown, a valid list will always be safely returned.
        * &lt;p&gt;
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is {@code null} then the instance
        *           will be dynamically queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations in which only a single
        *           value is needed.  This has negative performance implications in the case where
        *           multiple values are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various paths listed above
        *           (and in the precedence order specified above).
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   List of directory node ids or the an empty list if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       public static List<String> enumerateNodes(DirectoryService ds,
                                                 String           nodeId,
                                                 DirScope         scope,
                                                 String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper(null)
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.enumerateNodes(nodeId);
             }
          };

          String[] result = (String[]) getDirectoryNodeWorker(ds,
                                                              nodeId,
                                                              "container",
                                                              helper,
                                                              scope,
                                                              accountIds);

          return (result == null) ? Collections.emptyList() : Arrays.asList(result);
       }

       /**
        * Obtain the path to the node in the directory if it exists or return
        * <code>null</code> if the directory node doesn't exist with the proper
        * type, the directory paths don't exist or the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows paths to be
        * found that are account (user or process) specific or group specific
        * within the current server or a global default for all servers.
        * <p>
        * There are two search scopes: account relayed and server related.
        * Account related search is described below. The server related search
        * is a version of the search that is limited to the first two server
        * related levels.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * /server/&lt;serverID&gt;/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * If no user/process or group nodes are present, then this is checked:
        * /server/&lt;serverID&gt;/runtime/default/&lt;id&gt;
        * <p>
        * If no /server/&lt;serverID&gt;/runtime node exists, this is checked
        * (it is the global default area for all servers):
        * /server/default/runtime/&lt;account_or_group&gt;/&lt;id&gt;
        * <p>
        * Finally, if no user/process or group nodes are present in the global
        * default area, then this is checked:
        * /server/default/runtime/default/&lt;id&gt;
        * <p>
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    ds
        *           A bound directory service instance.  If this is
        *           <code>null</code> then the instance will be dynamically
        *           queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations
        *           in which only a single value is needed.  This has negative
        *           performance implications in the case where multiple values
        *           are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various
        *           paths listed above (and in the precedence order specified
        *           above).
        * @param    expected
        *           Expected class of node.  This will be checked with that
        *           found in the directory for consistency.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The path to the found node or <code>null</code> if for ANY
        *           reason the directory node cannot be found.
        */
       public static String findDirectoryNodePath(DirectoryService ds,
                                                  String           nodeId,
                                                  String           expected,
                                                  DirScope         scope,
                                                  String[]         accountIds)
       {
          DirectoryHelper helper = new DirectoryHelper("")
          {
             /**
              * @param ds
              */
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                // simply returns the path when called, this works because the
                // calling code has already checked the path for existence and
                // type matches
                return nodeId;
             }
          };

          return (String) getDirectoryNodeWorker(ds, nodeId, expected, helper,
                                                 scope, accountIds);
       }

       /**
        * Obtain the specified attribute from the directory if it exists or return the {@code null} if
        * the directory node doesn't exist with the proper type, the directory paths don't exist or
        * the directory is unavailable.
        * <p>
        * This method implements a search algorithm that allows values to be found that are account
        * (user or process) specific or group specific within the current server or a global default
        * for all servers.
        * <p>
        * The implementation iteratively looks up the directory node under:
        * <ol>
        *    <li>if a {@code projectToken} is configured (same stands for next all pairs below):<br>
        *       {@code /server/<serverID>/runtime/<account_or_group>/<id>.<project>};</li>
        *    <li>{@code /server/<serverID>/runtime/<account_or_group>/<id>};</li>
        *    <li>if no user/process or group nodes are present, then these checked:<br>
        *       {@code /server/<serverID>/runtime/default/<id>.<project>;}</li>
        *    <li>{@code /server/<serverID>/runtime/default/<id>;}</li>
        *    <li>if no {@code /server/<serverID>/runtime} node exists, these are checked (it is the
        *       global default area for all servers):<br>
        *       {@code /server/default/runtime/<account_or_group>/<id>.<project>;}</li>
        *    <li>{@code /server/default/runtime/<account_or_group>/<id>}</li>
        *    <li>finally, if no user/process or group nodes are present in the global default area,
        *       then these are checked:<br>
        *       {@code /server/default/runtime/default/<id>.<project>;}</li>
        *    <li>{@code /server/default/runtime/default/<id>}.</li>
        * </ol>
        * No exceptions are thrown, the requested attribute of {@code null} will always be safely
        * returned.
        * If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
        * performed.  If the value is not found, it fallbacks to a per-server search.
        *
        * @param    directoryService
        *           A bound directory service instance.  If this is {@code null} then the instance
        *           will be dynamically queried and bound for the duration of the method and then
        *           unbound at the end.  This is a convenience for situations in which only a single
        *           value is needed.  This has negative performance implications in the case where
        *           multiple values are sequentially obtained.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the various paths listed above
        *           (and in the precedence order specified above).
        * @param    expected
        *           Expected class of node or {@code null}. If given, this will be checked with that
        *           found in the directory for consistency.
        * @param    helper
        *           The data type-specific helper to query the given node.
        * @param    scope
        *           Search mode for the directory node lookup.
        * @param    accountIds
        *           Explicit list of account IDs which should be included in the search. When
        *           {@code null}, the current context's account IDs are resolved and used.
        *
        * @return   The requested value or {@code null} if for ANY reason the directory backed value
        *           cannot be obtained.
        */
       @SuppressWarnings("null")
       public static Object getDirectoryNodeWorker(DirectoryService directoryService,
                                                   String           nodeId,
                                                   String           expected,
                                                   DirectoryHelper  helper,
                                                   DirScope         scope,
                                                   final String[]   accountIds)
       {
          try
          {
             return doWithBoundDS(directoryService, false, ds ->
             {
                String projSpecNode = null; // compute project specific node
                String pToken = work.get().projectToken;
                if (pToken != null)
                {
                   projSpecNode = nodeId + "." + pToken;
                }

                // access the security manager
                SecurityManager sm = SecurityManager.getInstance();

                // any failure?
                if (sm == null && scope == DirScope.ACCOUNTS)
                {
                   // can't access the security manager
                   return null;
                }

                // obtain our context-specific values
                String serverId = ds.getServerId();
                String[] nullAcct = new String[]{null};
                String[] accIds = accountIds;
                if (accIds == null && (scope == DirScope.BOTH || scope == DirScope.ACCOUNTS))
                {
                   accIds = sm.getAccountIds();
                }

                // check account availability
                if (scope == DirScope.ACCOUNTS && accIds.length == 0)
                {
                   // something is wrong with our context
                   return null;
                }

                // our search algorithm's precedence order
                // 1. /server/<serverID>/runtime/<account_or_group>/<id>.<project>
                // 2. /server/<serverID>/runtime/<account_or_group>/<id>
                // 3. /server/<serverID>/runtime/default/<id>.<project>
                // 4. /server/<serverID>/runtime/default/<id>
                // 5. /server/default/runtime/<account_or_group>/<id>.<project>
                // 6. /server/default/runtime/<account_or_group>/<id>
                // 7. /server/default/runtime/default/<id>.<project>
                // 8. /server/default/runtime/default/<id>
                // or
                // 1. /server/<serverID>/<id>.<project>
                // 2. /server/<serverID>/<id>
                // 3. /server/default/<id>.<project>
                // 4. /server/default/<id>

                // if search scope is "BOTH", the precedence order is per-account then per-server.

                String cls = (expected == null) ? null : "/meta/class/" + expected;
                String pathSrv = "/server/" + serverId;

                // check the specific server first and then the global default
                String[] rootPaths = null;
                if (scope == DirScope.ACCOUNTS)
                {
                   rootPaths = new String[]
                   {
                   pathSrv + "/runtime",
                   "/server/default/runtime"
                   };
                }
                else if (scope == DirScope.SERVER)
                {
                   rootPaths = new String[]
                   {
                   pathSrv,
                   "/server/default"
                   };
                }
                else
                {
                   // use a fallback approach: first by account and last by server
                   rootPaths = new String[]
                   {
                   pathSrv + "/runtime",
                   "/server/default/runtime",
                   pathSrv,
                   "/server/default"
                   };
                }

                Object result = null;

                // loop through the target paths
                outer:
                for (int i = 0; i < rootPaths.length; i++)
                {
                   boolean onSrvPath = (scope == DirScope.SERVER || (scope == DirScope.BOTH && i >= 2));

                   // when searching per-server, use the "null" as account
                   // when searching per-account, use the real accounts
                   accIds = (onSrvPath ? nullAcct : accIds);

                   // check each account
                   for (int j = 0; j < accIds.length; j++)
                   {
                      if (projSpecNode != null)
                      {
                         result = executeLookup(ds, rootPaths[i], accIds[j], projSpecNode, cls, helper);
                         if (result != null)
                         {
                            break outer;
                         }
                      }
                      result = executeLookup(ds, rootPaths[i], accIds[j], nodeId, cls, helper);
                      if (result != null)
                      {
                         break outer;
                      }
                   }

                   if (onSrvPath)
                   {
                      continue;
                   }

                   if (projSpecNode != null)
                   {
                      result = executeLookup(ds, rootPaths[i], "default", projSpecNode, cls, helper);
                      if (result != null)
                      {
                         break outer;
                      }
                   }
                   // check for a default for all accounts
                   result = executeLookup(ds, rootPaths[i], "default", nodeId, cls, helper);
                   if (result != null)
                   {
                      break outer;
                   }
                }

                return result;
             });
          }
          catch (Exception exc)
          {
             // just allow null to be returned
             LOG.log(Level.WARNING, "An exception during query of Directory service.", exc);
          }

          return null;
       }

       /**
        * The method executes the supplied {@code code} with a bound Directory service.
        *
        * @param   ds
        *          If {@code null}, the method will query the service itself and bind it. If an instance is
        *          supplied, the method will bind it unless already bound.
        * @param   _throw
        *          otherwise the method returns {@code null}.
        *          When {@code true} RuntimeException is thrown when directory service can't be resolved,
        * @param   code
        *          The function to execute. The bound Directory service is passed in.
        *
        * @return  the value returned from {@code code}.
        */
       public static <T> T doWithBoundDS(DirectoryService ds, boolean _throw, Function<DirectoryService, T> code)
       {
          if (ds == null)
          {
             // access the directory service
             ds = DirectoryService.getInstance();

             // is the directory service is unavailable?
             if (ds == null)
             {
                // done!
                if (_throw)
                {
                   throw new RuntimeException("Directory service is unavailable!");
                }
                else
                {
                   return null;
                }
             }
          }

          boolean needsBind = !ds.isBound();

          // try to bind
          if (needsBind && !ds.bind())
          {
             // can't access the directory
             if (_throw)
             {
                throw new RuntimeException("Can not bind Directory service!");
             }
             else
             {
                return null;
             }
          }

          try
          {
             return code.apply(ds);
          }
          finally
          {
             // if we are one-shot, cleanup
             if (needsBind)
             {
                // last use of the directory, end our session with it
                ds.unbind();
             }
          }
       }

       /**
        * Gets the path resolver for the current directory settings.
        *
        * @param    fileSeparator
        *           The given legacy file separator
        * @param    caseSensitive
        *           The given case sensitive mode
        * @param    pathMap
        *           The given map of the legacy file system to the host file system
        *
        * @return   The path resolver
        */
       public static PathResolver getPathResolver(String              fileSeparator,
                                                  boolean             caseSensitive,
                                                  Map<String, String> pathMap)
       {
          PathResolverImpl resolver = new PathResolverImpl(fileSeparator, !caseSensitive);

          resolver.setPathMap(pathMap);

          return resolver;
       }

       /**
        * Gets the path resolver that silently logs all cases when UnResolvedPathException is thrown.
        *
        * @param    fileSeparator
        *           The given legacy file separator
        * @param    caseSensitive
        *           The given case sensitive mode
        * @param    pathMap
        *           The given map of the legacy file system to the host file system
        *
        * @return   The safe path resolver
        */
       public static PathResolverContainer getSafePathResolver(String              fileSeparator,
                                                               boolean             caseSensitive,
                                                               Map<String, String> pathMap)
       {
          PathResolver resolver = getPathResolver(fileSeparator, caseSensitive, pathMap);

          return new PathResolverContainer(resolver);
       }

       /**
        * Tries to set the temporary directory
        *
        * @param temporaryDirectory
        *    is the path of the temporary directory
        */
       public static void setTemporaryDirectory(String temporaryDirectory)
       {
          if (temporaryDirectory == null) return;

          Path path = Paths.get(temporaryDirectory);
          if (!Files.isDirectory(path) || !Files.isWritable(path))
          {
             String text = "Unable to open or create " + temporaryDirectory + ", error 2";
             PostInitErrorManager.addError(() -> ErrorManager.recordOrThrowError(354, text, false));
          };

          WorkArea wa = work.get();
          wa.temporaryDirectory = temporaryDirectory + File.separator + wa.defaultFwdTempFolder;
       }

       /**
        * Returns the explicitly set temporary directory
        *
        * @return the temporary directory
        */
       public static String getTemporaryDirectory()
       {
          return work.get().temporaryDirectory;
       }

       /**
        * Get or create the default directory for temporary files used by FWD.
        *
        * @return   The default directory for temporary files
        */
       public static String getOrCreateDefaultFwdTemporaryDirectory()
       {
          WorkArea wa = work.get();

          return wa.temporaryDirectory == null ?
             getOrCreateTemporaryDirectory(wa.defaultFwdTempFolder) :
             wa.temporaryDirectory;
       }

       /**
        * Get or create a directory for temporary files used by FWD.
        *
        * @param folderName
        *    The name of the folder to be created in the temporary directory.
        *
        * @return The directory for temporary files
        */
       public static String getOrCreateTemporaryDirectory(String folderName)
       {
          // setting up temporary files upload directory
          String tmpDir = getTmpDir(folderName);

          File tmpDirObj = new File(tmpDir);
          if (!tmpDirObj.exists())
          {
             tmpDirObj.mkdir();
             // add a JVM hook to delete this folder (and all its content).
             OrderedShutdownHooks.registerShutdownHook(1, new Thread(() ->
             {
                int res = FileSystemDaemon.delete(tmpDirObj, true);

                if (res != FileSystem.ERR_NO_ERROR)
                {
                   String path = tmpDirObj.getAbsolutePath();
                   LOG.log(Level.SEVERE, "Error " + res + " while removing the FWD temporary folder: " + path);
                }
             }));
          }
          // adding file separator to the end
          tmpDir = tmpDir.concat(File.separator);

          return tmpDir;
       }

       /**
        * This method returns the full path of the temporary folder.
        *
        * @param folderName
        *    The randomly generated temporary sub-folder name.
        *
        * @return The full path of the temporary folder.
        */
       private static String getTmpDir(String folderName)
       {
          String tmpDir = System.getProperty("java.io.tmpdir");

          if (!Files.isWritable(Paths.get(tmpDir)))
          {  // for the case the regular way above does not work
             tmpDir = System.getProperty("user.home");
          }

          return tmpDir + File.separator + folderName;
       }

       /**
        * Removes the specified directory node, including all child nodes.
        *
        * @param    ds
        *           A bound directory service instance with an editing batch open
        * @param    nodeId
        *           ID of node to remove
        *
        * @return   <code>true</code> if removed the node successfully
        */
       public static boolean removeDirectoryNode(DirectoryService ds, String nodeId)
       {
          // enumerate and remove all children first, recursively
          String[] child = ds.enumerateNodes(nodeId);
          if (child == null)
          {
             return false;
          }

          for (int i = 0; i < child.length; i++)
          {
             if (!removeDirectoryNode(ds, nodeId + "/" + child[i]))
             {
                return false;
             }
          }

          // remove the specified node now
          return ds.deleteNode(nodeId);
       }

       /**
        * Extract extension from the file name
        *
        * @param fileName
        *        file name
        *
        * @return extension (portion after the last dot)
        */
       public static String getExt(String fileName)
       {
          if (fileName == null)
          {
             return null;
          }
          String fn = new File(fileName).getName();
          int extPos = fn.lastIndexOf('.');
          return extPos < 0 ? "" : fn.substring(extPos + 1).trim();
       }

       /**
        * Get mimeType of the mouse cursor file
        *
        * @param fileName
        *        file name
        *
        * @return mimeType
        */
       public static String mimeType(String fileName)
       {
          return ImageExt.fromExt(getExt(fileName)).mimeType;
       }

       /**
        * Obtain the port number which has been mapped in the directory to a given
        * service name.  This is a partial replacement for the service normally
        * provided by Unix' <code>getservbyname()</code> API.
        * <p>
        * It is <i>partial</i> because it only will return a valid port number for
        * services explicitly mapped in the directory.  The current implementation
        * knows nothing of the mappings provided in the standard services file.
        * Thus, it should be used only for custom services mapped to ports in the
        * directory.
        * <p>
        * The following directory paths are checked for mappings in the following
        * precedence order:
        * <p>
        * <code>/server/&lt;server_id&gt;/port_services/&lt;service&gt;</code><br>
        * <code>/server/default/port_services/&lt;service&gt;</code>
        * <p>
        * where:
        * <ul>
        *   <li><code>&lt;server_id&gt;</code> is the unique identifier of the
        *       current P2J server instance;  and</li>
        *   <li><code>&lt;service&gt;</code> is the service name given by the
        *       <code>name</code> parameter to this method.</li>
        * </ul>
        *
        * @param   ds
        *          A bound directory service instance.  If this is
        *          <code>null</code> then the instance will be dynamically
        *          queried and bound for the duration of the method and then
        *          unbound at the end.  This is a convenience for situations
        *          in which only a single value is needed.  This has negative
        *          performance implications in the case where multiple values
        *          are sequentially obtained.
        * @param   name
        *          Service name to look up.
        *
        * @return  The requested value or -1 if the given service is not mapped to
        *          any port number in the directory paths listed above.
        */
       public static int getPortForService(DirectoryService ds, String name)
       {
          DirectoryHelper helper = new DirectoryHelper("value")
          {
             @Override
             public Object getDirectoryNode(DirectoryService ds, String nodeId)
             {
                return ds.getNodeInteger(nodeId, attrName);
             }
          };

          StringBuilder buf = new StringBuilder("port_services/");
          buf.append(name);
          Integer result = (Integer) getDirectoryNodeWorker(ds,
                                                            buf.toString(),
                                                            "integer",
                                                            helper,
                                                            DirScope.SERVER,
                                                            null);

          return ((result == null) ? -1 : result.intValue());
       }

       /**
        * Serves as a placeholder for some interactive help.
        */
       public static void noHelp()
       {
          LogicalTerminal.messageBox("No application help is available");
       }

       /**
        * Get the short name of a class or interface (everything after the last
        * dot in the fully qualified class name).
        *
        * @param   cls
        *          Class.
        *
        * @return  Unqualified class name.
        */
       public static String getUnqualifiedName(Class<?> cls)
       {
          String name = cls.getName();

          return name.substring(name.lastIndexOf(".") + 1);
       }

       /**
        * Apply function to the list elements while the result is <code>null</code>
        *
        * @param list
        *        the list to be traversed
        * @param fn
        *        Function to be applied
        *
        * @return the first non-null result or <code>null</code> if none found
        *
        */
       public static <T,S> T tryWhileNull(List<S> list, Function<S,T> fn)
       {
          return  list.stream().map(fn).
                filter(r -> r != null).findFirst().orElse(null);
       }

       /**
        * Find as many files as case-insensitively match the file specified by
        * <code>fullPath</code>, where the directory specified by
        * <code>leading</code> represents a leading portion of the given path.
        * <p>
        * This method calls itself recursively with progressively more specific
        * <code>leading</code> directories, until <code>leading</code> represents
        * the full path qualifier for the file which is the target of the match.
        * <p>
        * All paths in this method are processed using the current system's file
        * separator character.  This means that any input to this method must
        * have already been properly converted to the system file separator.
        *
        * @param   leading
        *          A leading portion of the directory qualifier for the
        *          <code>fullPath</code> argument. The top-level call to this
        *          method would pass <code>null</code> here.  Recursive calls will
        *          pass a value. If given, this MUST be a valid directory in the
        *          file system AND it must be a valid prefix for the target file
        *          (it must case-insensitively match the begining of the target
        *          filename).
        * @param   fullPath
        *          A canonicalized path name which represents an absolute path of
        *          the file to be matched.  Must NOT be <code>null</code>.
        * @param   list
        *          A list in which to store matching <code>File</code> objects
        *          (which is the result of this processing).  Must NOT be
        *          <code>null</code>.
        */
       private static void matchPathCaseInsensitively(File leading,
                                                      String fullPath,
                                                      List<File> list)
       {
          final String SEP = File.separator;
          int pos = 0;

          // process leading portion (pull out the directory name and calculate
          // its length when ending with the file separator character)
          if (leading != null)
          {
             String dirName = leading.getAbsolutePath();
             pos = dirName.length();

             // safety check
             if (!dirName.equalsIgnoreCase(fullPath.substring(0, pos)))
             {
                // TODO: add error reporting/logging

                // leading portion cannot match the target file name
                return;
             }

             if (!dirName.endsWith(SEP))
             {
                pos += SEP.length(); // always 1, from File.separator javadoc
             }
          }

          // gather everything after the leading portion
          String remaining = fullPath.substring(pos);

          // find the end of the next path segment; this is the last segment (which
          // is the base file name itself) if this is -1
          pos = remaining.indexOf(SEP);

          // extract the portion of the path which corresponds with the directory
          // level we're currently scanning; this is a path segment only
          final String match = (pos >= 0
                                ? remaining.substring(0, pos)
                                : remaining);

          // file filter which matches case-insensitively on the match string
          final FileFilter ff = new FileFilter()
          {
             @Override
             public boolean accept(File test)
             {
                // this only checks one segment at a time, not portions that
                // "cross" or include file separator characters
                String name = test.getName();
                if (name.isEmpty())
                {
                   // special care on windows: when comparing the drive (C:\) segments the name
                   // is empty because there is no directory.
                   // manually comparing the drives:
                   return test.getAbsolutePath().equalsIgnoreCase(match + SEP);
                }

                return name.equalsIgnoreCase(match);
             }
          };

          File[] files = null;

          if (leading == null)
          {
             // top-level call to this method

             // special case:  no leading directory specified, so we start at the
             // file system roots and try to match the portion of our path up to
             // the first file separator
             File[] roots = File.listRoots();
             List<File> l = new ArrayList<>(roots.length);
             for (File next : roots)
             {
                if (ff.accept(next))
                {
                   l.add(next);
                }
             }

             files = l.toArray(new File[l.size()]);
          }
          else
          {
             // recursive call to this method

             // safety code; leading must always be a directory
             if (!leading.isDirectory())
             {
                // TODO: add error reporting/logging

                // this is definitely not a match
                return;
             }

             files = leading.listFiles(ff);

             if (files == null)
             {
                // TODO: add error reporting/logging

                // this should only occur on an I/O error
                return;
             }
          }

          if (pos < 0)
          {
             // last level of search; the contents of the files array contains our final result(s)
              Collections.addAll(list, files);
          }
          else
          {
             // search recursively down through next set of matching directories
             for (File next : files)
             {
                // file matches cannot be checked further using recursion and they
                // can't match anyway, so avoid them
                if (next.isDirectory())
                {
                    matchPathCaseInsensitively(next, fullPath, list);
                }
             }
          }
       }

       /**
        * Builds the node path from its parts, checks that it exists with the
        * correct data type and if so, returns the value.  The node path that
        * will be searched is:
        * <p>
        * <pre>
        * root + "/" + postfix + "/" + nodeId
        * </pre>
        *
        * @param    ds
        *           A bound directory service instance.  Must not be
        *           <code>null</code>.
        * @param    root
        *           First part of the path to build.
        * @param    postfix
        *           Second part of the path to build or <code>null</code>.
        * @param    nodeId
        *           ID of node to obtain.  This will be appended to the two
        *           paths listed above.
        * @param    expected
        *           Expected class of node or <code>null</code>. If given, this
        *           will be checked with that found in the directory for
        *           consistency.
        * @param    helper
        *           The data type-specific helper to query the given node.
        *
        * @return   The requested value or <code>null</code> if for ANY
        *           reason the directory backed value cannot be obtained.
        */
       private static Object executeLookup(DirectoryService ds,
                                           String           root,
                                           String           postfix,
                                           String           nodeId,
                                           String           expected,
                                           DirectoryHelper  helper)
       {
          StringBuilder path = new StringBuilder();
          path.append(root).append("/");
          if (postfix != null)
          {
             path.append(postfix).append("/");
          }
          path.append(nodeId);

          String current   = path.toString();
          String nodeClass = ds.getNodeClass(current);

          if (nodeClass != null)
          {
             if (expected == null || nodeClass.equals(expected))
             {
                // we found our path!
                return helper.getDirectoryNode(ds, current);
             }
             else
             {
                if (LOG.isLoggable(Level.WARNING))
                {
                   final String msg = "Node %s is expected to have class %s, but is configured as %s.";
                   LOG.logp(Level.WARNING, "Utils", "executeLookup",
                            String.format(msg, current, expected, nodeClass));
                }
             }
          }

          // nothing found or data type is incorrect, continue search
          return null;
       }

       /**
        * Generic type safe equals. It can be used for <code>null</code> objects
        * without fear of NPE. If both provided objects are <code>null</code> then
        * they are assumed equal.
        * @param   <T>
        *          Type parameter.
        * @param   obj1
        *          First object for comparison.
        * @param   obj2
        *          Second object for comparison.
        *
        * @return  <code>true</code> if objects are equal.
        */
       public static<T> boolean equals(T obj1, T obj2)
       {
          if (obj1 == null && obj2 == null)
          {
             return true;
          }

          return (obj1 != null) ? obj1.equals(obj2) : obj2.equals(obj1);
       }

       /**
        * Provides a simple unified API for delegating the querying of a
        * data type-specific value from the directory.
        */
       private static abstract class DirectoryHelper
       {
          /** Attribute name to obtain. */
          protected String attrName = null;

          /**
           * Construct an instance and save the attribute name for future use.
           *
           * @param    attrName
           *           The name of the attribute to obtain.
           */
          public DirectoryHelper(String attrName)
          {
             this.attrName = attrName;
          }

          /**
           * User-implemented method which queries the given directory service
           * for the attribute value of the given node ID and the returns the
           * result.  The implementation of this method if data type-specific
           * however the result is returned generically.
           * <p>
           * The attribute name provided at construction time is the one to use
           * during the query.  This eliminates the need to pass the attribute
           * name to the code to which the call is delegated.
           *
           * @param    ds
           *           A bound directory service instance.  Must not be
           *           <code>null</code>.
           * @param    nodeId
           *           The full path of the node to obtain.
           *
           * @return   Attribute value.
           */
          public abstract Object getDirectoryNode(DirectoryService ds, String nodeId);
       }

       /**
        * Returns a context-wide unique integer value which can be used as an ID.
        * If the internal counter reaches the maximum value, it wraps around to 0.
        * <p>
        * @return   next available integer value
        */
       public static int uniqueId()
       {
          WorkArea wa = work.get();

          int newValue = 0;

          synchronized (wa)
          {
             newValue = ++wa.counter;
             if (newValue == Integer.MAX_VALUE)
             {
                wa.counter = -1;
             }
          }

          return newValue;
       }

       /**
        * Set the project token for this context. Every access to directory will return values
        * specific to this project. If a specific value is not found, the default/global one will be
        * returned if it exist.
        *
        * The project can be set only once, so this method can be called only once for respective
        * context.
        *
        * @param   token
        *          the token that identifies the project.
        *
        * @throws  IllegalArgumentException
        *          if the project was already set for current context.
        */
       public static void setProjectToken(String token)
       {
          WorkArea workArea = work.get();

          if ((workArea.projectToken == null && token == null) ||
             (workArea.projectToken != null && workArea.projectToken.equals(token)))
          {
             // same token, nothing to do
             return;
          }

          if (workArea.projectToken != null)
          {
             LOG.log(Level.WARNING,
                     "The project token can be set only once. Current token %s. Attempt to set token %s.",
                     workArea.projectToken,
                     token);
             return;
          }

          workArea.projectToken = token;
       }

       /**
        * Get the current project token. If it was not set, {@code null} is returned.
        *
        * @return  the current project token or {@code null} if not previously set.
        */
       public static String getProjectToken()
       {
          WorkArea workArea = work.get();
          return workArea.projectToken;
       }

       /**
        * Check if the two sets are the same. Note that <code>null</code> instances are allowed for
        * either set.
        *
        * @param    a
        *           The first set.
        * @param    b
        *           The second set.
        *
        * @return   <code>true</code> if the sets match.
        */
       public static<T> boolean isSame(Set<T> a, Set<T> b)
       {
          if (a == null)
          {
             return b == null;
          }
          else if (b == null)
          {
             return false;
          }
          return a.size() == b.size() && a.containsAll(b);
       }

       /**
        * Check if a given value is a valid value.
        *
        * @param    value
        *           Value to check.
        * @param    validValues
        *           List of valid values.
        *
        * @return   <code>true</code> if value is in the list of valid values,
        *           otherwise <code>false</code>.
        */
       public static boolean isValidValue(int value, int ... validValues)
       {
          for (int v : validValues)
          {
             if (value == v)
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Determine if we are on the server or not.
        *
        * @return   <code>true</code> if we are running on the server.
        */
       public static boolean isServer()
       {
          SecurityManager sm = SecurityManager.getInstance();
          return sm != null && sm.isServer();
       }

       /**
        * Determine if we are on the client or not.
        *
        * @return   <code>true</code> if we are running on the client.
        */
       public static boolean isClient()
       {
          return !isServer();
       }

       /**
        * Determine if we are on ChUI or not.
        *
        * @return   <code>true</code> if we are running ChUI.
        */
       public static boolean isChUI()
       {
          return isServer() ? LogicalTerminal.isChui() : ThinClient.getInstance().isChui();
       }

       /**
        * Determine if we are on GUI or not.
        *
        * @return   <code>true</code> if we are running GUI.
        */
       public static boolean isGUI()
       {
          return !isChUI();
       }

       /**
        * Determines whether two sets have common elements.
        *
        * @param <T>
        *
        * @param    a
        *           a set
        * @param    b
        *           another set
        *
        * @return   <code>true</code> if there are common elements
        */
       public static<T> boolean intersect(Set<T> a, Set<T> b)
       {
          if (a == null || b == null || a.isEmpty() || b.isEmpty())
          {
             return false;
          }

          int aSz  = a.size();
          int bSz  = b.size();

          // iterate the smaller of the two sets
          Iterator<T> iter = aSz <= bSz ? a.iterator() : b.iterator();
          Set<T>      comp = aSz <= bSz ? b : a;

          while (iter.hasNext())
          {
             if (comp.contains(iter.next()))
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Determines whether two sets have common elements.
        *
        * @param    a
        *           a set
        * @param    b
        *           another set
        *
        * @return   <code>true</code> if there are common elements
        */
       public static boolean intersect(RoaringBitmap a, RoaringBitmap b)
       {
          if (a == null || b == null || a.isEmpty() || b.isEmpty())
          {
             return false;
          }

          int aSz  = a.getCardinality();
          int bSz  = b.getCardinality();

          // iterate the smaller of the two sets
          Iterator<Integer> iter = aSz <= bSz ? a.iterator() : b.iterator();
          RoaringBitmap     comp = aSz <= bSz ? b : a;

          while (iter.hasNext())
          {
             if (comp.contains(iter.next()))
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Determines whether two sets have common elements.
        *
        * @param    a
        *           a set
        * @param    b
        *           another set
        *
        * @return   <code>true</code> if there are common elements
        */
       public static boolean intersect(Roaring64Bitmap a, Roaring64Bitmap b)
       {
          if (a == null || b == null || a.isEmpty() || b.isEmpty())
          {
             return false;
          }

          int aSz  = a.getIntCardinality();
          int bSz  = b.getIntCardinality();

          // iterate the smaller of the two sets
          Iterator<Long>  iter = aSz <= bSz ? a.iterator() : b.iterator();
          Roaring64Bitmap comp = aSz <= bSz ? b : a;

          while (iter.hasNext())
          {
             if (comp.contains(iter.next()))
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Check if the given instance contains all elements from the other instance.
        *
        * @param    instance
        *           The first instance.
        * @param    other
        *           The other instance.
        *
        * @return   <code>true</code> if the difference between the 'other' and 'instance' is empty.
        */
       public static boolean containsAll(RoaringBitmap instance, RoaringBitmap other)
       {
          if (instance.getCardinality() < other.getCardinality())
          {
             return false;
          }

          Iterator<Integer> iter = other.iterator();
          while (iter.hasNext())
          {
             if (!instance.contains(iter.next()))
             {
                return false;
             }
          }

          return true;
       }

       /**
        * Check if the given instance contains all elements from the other instance.
        *
        * @param    instance
        *           The first instance.
        * @param    other
        *           The other instance.
        *
        * @return   <code>true</code> if the difference between the 'other' and 'instance' is empty.
        */
       public static boolean containsAll(Roaring64Bitmap instance, Roaring64Bitmap other)
       {
          if (instance.getIntCardinality() < other.getIntCardinality())
          {
             return false;
          }

          Iterator<Long> iter = other.iterator();
          while (iter.hasNext())
          {
             if (!instance.contains(iter.next()))
             {
                return false;
             }
          }

          return true;
       }

       /**
        * Produces both the intersection and difference of two sets and puts those values into the
        * given result set instances.
        *
        * @param    <T>
        *           The type of the set elements.
        * @param    a
        *           a set
        * @param    b
        *           another set
        * @param    inter
        *           The result set which will contain the intersection results.
        * @param    diff
        *           The result set which will contain the difference results.  This is the set of items in "a"
        *           which are not in "b", but it does not include items in "b" which are not in "a".
        */
       public static<T> void split(Set<T> a, Set<T> b, Set<T> inter, Set<T> diff)
       {
          inter.clear();
          diff.clear();

          Iterator<T> iter = a.iterator();
          T o = null;

          while (iter.hasNext())
          {
             o = iter.next();

             if (b.contains(o))
             {
                inter.add(o);
             }
             else
             {
                diff.add(o);
             }
          }
       }

       /**
        * Produces the intersection of two sets and puts it into the third set.
        *
        * @param <T>
        *
        * @param    a
        *           a set
        * @param    b
        *           another set
        * @param    result
        *           result set
        */
       public static<T> void intersection(Set<T> a, Set<T> b, Set<T> result)
       {
          result.clear();

          int aSz  = a.size();
          int bSz  = b.size();

          // iterate the smaller of the two sets
          Iterator<T> iter = aSz <= bSz ? a.iterator() : b.iterator();
          Set<T>      comp = aSz <= bSz ? b : a;

          while (iter.hasNext())
          {
             T t = iter.next();

             if (comp.contains(t))
             {
                result.add(t);
             }
          }
       }

       /**
        * Produces the difference of two sets and puts it into the third set.
        *
        * @param    <T>
        *           The element type.
        * @param    a
        *           The first set.
        * @param    b
        *           The set to subtract from 'a'.
        * @param    result
        *           The result set which will contain the difference results.  This is the set of items in "a"
        *           which are not in "b", but it does not include items in "b" which are not in "a".
        */
       public static<T> void difference(Set<T> a, Set<T> b, Set<T> result)
       {
          result.clear();

          Iterator<T> iter = a.iterator();
          T o = null;

          while (iter.hasNext())
          {
             o = iter.next();

             if (!b.contains(o))
             {
                result.add(o);
             }
          }
       }

       /**
        * Gets a resource path string to be cleared from invalid sequences and file separator duplication.
        * Handles properly current('.') and uplevel('..') directory entries and empty spaces.  No case
        * letter changes are performing.
        *
        * @param    nameDirty
        *           The not yet refined resource path string.
        * @param    separator
        *           The string representing file separator to use in result.
        *
        * @return   Properly formatted case sensitive image path string.
        */
       public static String getRefinedPathName(String nameDirty, String separator)
       {
          synchronized (getPathCache())
          {
             String cachedPath = getPathCache().get(nameDirty + separator);
             if (cachedPath != null)
             {
                return cachedPath;
             }
          }
          // parse the dirty path into array by separator to clean for multiple separators
          String[] splitPath = nameDirty.split(separator);
          int pathSize = splitPath.length;

          // walk through the entries to clean up
          for (int i = 0; i < pathSize; i++)
          {
             // process next path name entry
             String name = splitPath[i];

             // skip the current directory, empty directory, directory or files with more than 2 dots
             // in beginning
             if (name.equals(".") || name.isEmpty() || (name.startsWith("..") && !name.equals("..")))
             {
                // go one directory level up
                for (int j = i; j < pathSize - 1; j++)
                {
                   splitPath[j] = splitPath[j + 1];
                }
                // one entry has been removed for current dir
                pathSize--;
                i--;

                continue;
             }

             // try to handle parent directory shift
             if (name.equals(".."))
             {
                // for the first and last entries just ignore this - no parent directory
                if (i > 0 && i < pathSize - 1)
                {
                   // go one directory level up
                   for (int j = i; j < pathSize - 1; j++)
                   {
                      splitPath[j - 1] = splitPath[j + 1];
                   }
                   i--;
                }
                // two entries have been removed, one for '..' and parent directory
                pathSize -= 2;
                continue;
             }

          }

          // second pass - build the final buffer
          StringBuilder sb = new StringBuilder();
          // at this time we must have clean array
          for (int i = 0; i < pathSize; i++)
          {
             sb.append(splitPath[i]);

             // for all entries except last add the separator
             if (i < pathSize - 1)
             {
                sb.append(separator);
             }
          }

          // fix the directory path
          if (nameDirty.endsWith(separator) && !sb.toString().endsWith(separator))
          {
             sb.append(separator);
          }

          String result = sb.toString();
          synchronized (getPathCache())
          {
             getPathCache().putIfAbsent(nameDirty + separator, result);
          }

          return result;
       }

       /**
        * Get the path of the jar file containing the given class.
        *
        * @param    clazz
        *           The given class
        *
        * @return   The path of the jar file if it exists, otherwise null.
        */
       public static String getJarPath(Class clazz)
       {
          CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
          File jarFile;
          try
          {
             if (codeSource.getLocation() != null)
             {
                jarFile = new File(codeSource.getLocation().toURI());
             }
             else
             {
               String path = clazz.getResource(clazz.getSimpleName() + ".class").getPath();
               String jarFilePath = path.substring(path.indexOf(":") + 1, path.indexOf("!"));
               jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
               jarFile = new File(jarFilePath);
             }
          }
          catch (URISyntaxException | UnsupportedEncodingException e)
          {
             LOG.severe("", e);
             return null;
          }

          return jarFile.getPath();
       }

       /**
        * Produces the difference of two int arrays.
        *
        * @param    a
        *           The first array.
        * @param    b
        *           The second array which is to be subtracted from the first.
        *
        * @return   All elements from the first array that are not in the
        *           second array.
        */
       public static int[] diffIntArrays(int[] a, int[] b)
       {
          // boundary cases
          if (a.length == 0)
          {
             return new int[] {};
          }

          int[] aa = new int[a.length];
          for (int i = 0; i < a.length; i ++)
          {
             aa[i] = a[i];
          }

          Arrays.sort(aa);

          if (b.length == 0)
          {
             return aa;
          }

          int[] bb = new int[b.length];
          for (int i = 0; i < b.length; i ++)
          {
             bb[i] = b[i];
          }

          Arrays.sort(bb);

          int[] res = new int[aa.length];
          int count = 0;
          int i = 0;
          int j = 0;

          scan:
          for (i = 0; i < aa.length; i ++)
          {
             // equal numbers; skip both
             if (aa[i] == bb[j])
             {
                j ++;
                if (j < bb.length)
                {
                   continue;
                }

                break scan;
             }

             // integer from a to keep
             if (a[i] < b[j])
             {
                res[count ++] = a[i];
                continue;
             }

             // integer from b to skip
             while (a[i] > b[j])
             {
                j ++;
                if (j < bb.length)
                {
                   continue;
                }

                break scan;
             }
          }

          // copy the rest of a
          for (int k = i + 1; k < aa.length; k ++)
          {
             res[count ++] = aa[k];
          }

          // compact the array
          int[] diff = new int[count];
          for (i = 0; i < count; i ++)
          {
             diff[i] = res[i];
          }

          return diff;
       }

       /**
        * The method returns a reverse {@linkplain java.util.stream.Stream}
        * from the supplied <code>array</code>.
        *
        * @param    array
        *           The array to be reversed in a stream.
        *
        * @return   see above.
        */
       public static <T> java.util.stream.Stream<T> reverseStream(T[] array)
       {
          int size = array.length;
          return IntStream.rangeClosed(0, size - 1)
                          .mapToObj(i -> array[size - i - 1]);
       }

       /**
        * The method returns a reverse {@linkplain java.util.stream.Stream}
        * from the supplied linked list.
        *
        * @param    list
        *           The linked list to be reversed in a stream.
        *
        * @return   see above.
        */
       public static <T> java.util.stream.Stream<T> reverseStream(LinkedList<T> list)
       {
          Iterator<T> it = list.descendingIterator();
          Spliterator<T> sit = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED);
          return StreamSupport.stream(sit, false);
       }

       /**
        * The method returns a reverse {@linkplain java.util.stream.Stream}
        * from the supplied <code>list</code>.
        *
        * @param    list
        *           The list to be reversed in a stream.
        *
        * @return   see above.
        */
       public static <T> java.util.stream.Stream<T> reverseStream(List<T> list)
       {
          if (list instanceof LinkedList<?>)
          {
             return reverseStream((LinkedList<T>) list);
          }
          else
          {
             int size = list.size();
             return IntStream.rangeClosed(0, size - 1)
                             .mapToObj(i -> list.get(size - i - 1));
          }
       }

       /**
        * The method returns the first non-null argument from the supplied array of
        * arguments.
        *
        * @param    args
        *           An array of arguments.
        *
        * @return   See above.
        *
        * @throws   IllegalArgumentException
        *           when zero arguments is passed to the method.
        * @throws   NullPointerException
        *           when no non-null value is found in the supplied array of arguments.
        */
       @SafeVarargs
       public static <T> T nonNull(T... args)
       {
          if (args == null || args.length == 0)
          {
             throw new IllegalArgumentException();
          }

          for (T item : args)
          {
             if (item != null)
             {
                return item;
             }
          }

          throw new NullPointerException();
       }

       /**
        * The method returns the first non-empty trimmed string represented by the supplied
        * arguments. {@linkplain Object#toString()} is used to convert each argument to a
        * string value and trimmed by {@linkplain String#trim()}.
        *
        * @param    args
        *           An array of arguments.
        *
        * @return   See above.
        *
        * @throws   IllegalArgumentException
        *           when zero arguments is passed to the method or no non-empty string is
        *           represented by any of the supplied arguments.
        */
       @SafeVarargs
       public static String nonEmpty(Object... args)
       {
          if (args == null || args.length == 0)
          {
             throw new IllegalArgumentException();
          }

          for (Object item : args)
          {
             if (item == null)
             {
                continue;
             }

             String str = item.toString().trim();
             if (!str.isEmpty())
             {
                return str;
             }
          }

          throw new IllegalArgumentException();
       }

       /**
        * Reorder widgets according to their ZOrderClass retaining the order within the class.
        *
        * @param    widgets
        *           The widget collection
        * @param    zorder
        *           The given z-order function defined on widgets
        *
        * @return   The modified resorted widgets collection.
        */
       public static <W> Collection<W> normalizeZOrder(Collection<W> widgets,
                                                       Function<W, ZOrderClass> zorder)
       {
          Map<ZKey, W> m = new TreeMap<ZKey, W>();

          int i = 0;
          for (W w: widgets)
          {
             m.put(new ZKey(zorder.apply(w), i++), w);
          }
          widgets.clear();
          widgets.addAll(m.values());

          return widgets;
       }

       /**
        * Move the specified widget to the bottom of the widget list considering Z-order class.
        * @param <W>
        *
        * @param    widgets
        *           The given widgets list.
        * @param    widget
        *           The given widget to move.
        * @param    zorder
        *           function to get ZOrderClass for the widget
        *
        * @return   True if the given widget is changed its position in the given list.
        */
       public static <W> boolean moveToBottomInClass(List<W> widgets,
                                                     W widget,
                                                     Function<W, ZOrderClass> zorder)
       {
          final int pos = widgets.indexOf(widget);
          if (pos < 0)
          {
             return false;
          }

          // remove it now, it will always be re-added
          widgets.remove(pos);

          final int size = widgets.size();
          final int cls = zorder.apply(widget).ordinal();
          int limit = size;
          int np = size;
          boolean found = false;
          // find first widget in the same z-class and move it in its location (before it);
          // if there are no widgets in the same z-class, then put it on top
          for (int i = 0; i < limit; i++)
          {
             final W w = widgets.get(i);
             int wcls = zorder.apply(w).ordinal();

             if (wcls == cls)
             {
                found = true;
                np = i;
                break;
             }
          }

          // move it to the correct location (it may be the same)
          if (found)
          {
             widgets.add(np, widget);
          }
          else
          {
             widgets.add(0, widget);
          }
          // this takes care of re-ordering the widgets by their z-class (keeping the order in each
          // specific class)
          normalizeZOrder(widgets, zorder);

          // return true only if the widget's position has moved
          return np != pos;
       }

       /**
        * Move specified widget to the top of the given widget list considering Z-order class.
        * @param <W>
        *
        * @param    widgets
        *           The given widgets list.
        * @param    widget
        *           The given widget to move.
        * @param    zorder
        *           function to get ZOrderClass for the widget
        *
        * @return   True if the given widget is changed its position in the given list.
        */
       public static <W> boolean moveToTopInClass(List<W> widgets,
                                                  W widget,
                                                  Function<W, ZOrderClass> zorder)
       {
          int pos = widgets.indexOf(widget);
          if (pos < 0)
          {
             return false;
          }

          // remove it now, it will always be re-added
          widgets.remove(pos);

          int size = widgets.size();
          int cls = zorder.apply(widget).ordinal();
          int np = size;
          boolean found = false;
          // find last widget in the same z-class and move it after it; if there are no widgets
          // in the same z-class, then put it on top
          for (int i = size - 1; i >= 0; i--)
          {
             W w = widgets.get(i);
             int wcls = zorder.apply(w).ordinal();

             if (wcls == cls)
             {
                // found the new class, keep it
                found = true;
                np = i;
                break;
             }
          }

          if (found)
          {
             // insert widget at this position
             np = np + 1;
          }

          // move it to the correct location (it may be the same)
          if (np >= size)
          {
             widgets.add(widget);
          }
          else
          {
             widgets.add(np, widget);
          }

          // this takes care of re-ordering the widgets by their z-class (keeping the order in each
          // specific class)
          normalizeZOrder(widgets, zorder);

          // return true only if the widget's position has moved
          return np != pos;
       }

       /**
        * Remove all items from that collection which satisfy the given condition
        *
        * @param c
        *        the collection
        * @param p
        *        the condition
        */
       public static <T> void removeAllIf(Collection<T> c, Predicate<T> p)
       {
          if (c == null)
          {
             return;
          }
          for (Iterator<T> iter = c.iterator(); iter.hasNext(); )
          {
             if (p.test(iter.next()))
             {
                iter.remove();
             }
          }
       }

       /**
        * Converts string size value to integer one.  The string can be suffixed with MB or KB or none
        * for megabyes base init.  The input string must be non null and not empty.
        *
        * @param    sizeStr
        *           An string representing the integer limit value.
        *
        * @return   see above.
        */
       public static int getSizeLimitFromString(String sizeStr)
       {
          // default unit will be bytes
          int res = 1;

          // prepare string
          sizeStr = sizeStr.toUpperCase().trim();

          // analyze the string
          StringBuilder sb = new StringBuilder();
          for (int i = 0; i < sizeStr.length(); i++)
          {
             char chNext = sizeStr.charAt(i);
             if (Character.isDigit(chNext))
             {
                sb.append(chNext);
             }
             else
             {
                if (chNext == 'M')
                {
                   // the base units are megabytes
                   res = 1024 * 1024;
                   break;
                }
                else if (chNext == 'K')
                {
                   // the base units are kilobytes
                   res = 1024;
                   break;
                }
                else
                {
                   // ignore other chars
                   continue;
                }
             }
          }

          // now string builder must have only numbers
          res *= Integer.valueOf(sb.toString()).intValue();

          return res;
       }

       /**
        * Converts long number to little-endian bytes.
        *
        * @param   l
        *          A long number.
        *
        * @return  the returned byte array.
        */
       public static byte[] longToBytesLE(long l)
       {
          return new byte[]
          {
             (byte) l,
             (byte) (l >> 8),
             (byte) (l >> 16),
             (byte) (l >> 24),
             (byte) (l >> 32),
             (byte) (l >> 40),
             (byte) (l >> 48),
             (byte) (l >> 56)
          };
       }

       /**
        * Converts little-endian bytes to long number.
        *
        * @param   data
        *          Bytes to convert.
        *
        * @return  the returned long.
        */
       public static long bytesToLongLE(byte[] data)
       {
          long value = data[0] & 0xffL;
          value += (data[1] & 0xffL) << 8;
          value += (data[2] & 0xffL) << 16;
          value += (data[3] & 0xffL) << 24;
          value += (data[4] & 0xffL) << 32;
          value += (data[5] & 0xffL) << 40;
          value += (data[6] & 0xffL) << 48;
          value += (data[7] & 0xffL) << 56;
          return value;
       }

       /**
        * Find a field with the specified value, in given class.
        *
        * @param    instance
        *           The instance where to look for the field.
        * @param    cls
        *           The field is defined in this class or one of its super-classes.
        * @param    val
        *           The field must have this exact value, to be found.
        *
        * @return   The found field, or <code>null</code> if no field is holding the expected value.
        */
       public static Field findFieldWithValue(Object instance, Class<?> cls, Object val)
       {
          if (cls == null)
          {
             return null;
          }

          Field[] fields = cls.getDeclaredFields();
          for (Field f : fields)
          {
             f.setAccessible(true);
             try
             {
                Object fval = f.get(instance);

                if (fval instanceof ContextLocal)
                {
                   fval = ((ContextLocal) fval).get();
                }

                if (val == fval)
                {
                   return f;
                }
             }
             catch (Exception e)
             {
                LOG.warning("", e);
             }
          }

          return findFieldWithValue(instance, cls.getSuperclass(), val);
       }

       /**
        * Collect all the implemented interfaces (directly and from the hierarchy), for the given
        * class.
        * <p>
        * The collection is performed doing a BFS walk of the hierarchy, starting from the given class.
        *
        * @param    cls
        *           The class from where to get the interfaces.
        * @param    interfaces
        *           A set where to store the interfaces, in the BFS order..
        */
       public static void collectInterfaces(Class<?> cls, Set<Class<?>> interfaces)
       {
          collectInterfacesImpl(cls, interfaces, new HashSet<>());
       }

       /**
        * Do a DFS walk of the class hierarchy and execute the given task when going 'back up' on the tree.
        *
        * @param    cls
        *           The class to start the walk.
        * @param    task
        *           The task to execute.
        */
       public static void dfsClassHierarchy(Class<?> cls, Consumer<Class<?>> task)
       {
          Class<?>[] ifcs = cls.getInterfaces();
          if (ifcs != null)
          {
             for (Class<?> ifc : ifcs)
             {
                dfsClassHierarchy(ifc, task);
             }
          }

          if (cls.getSuperclass() != null)
          {
             dfsClassHierarchy(cls.getSuperclass(), task);
          }

          task.accept(cls);
       }

       /**
        * Reads a row of CSV values from the reader and returns the values as a Java list.
        *
        * @param   r
        *          Reader to read from.
        *
        * @return  A valid list or {@code null} if end of stream is reached.
        *
        * @throws IOException
        */
       public static List<String> readCSVLine(Reader r)
       throws IOException
       {
          int ch = r.read();

          // skip CR
          while (ch == '\r')
          {
             ch = r.read();
          }

          if (ch < 0)
          {
             return null;
          }

          List<String> res = new ArrayList<>();
          StringBuffer curVal = new StringBuffer();
          boolean quotes = false;
          boolean inval = false;

          while (ch >= 0)
          {
             if (quotes)
             {
                inval = true;
                if (ch == '\"')
                {
                   quotes = false;
                }
                else
                {
                   curVal.append((char) ch);
                }
             }
             else
             {
                if (ch == '\"')
                {
                   quotes = true;
                   if (inval)
                   {
                      curVal.append('\"');
                   }
                }
                else if (ch == ',')
                {
                   res.add(curVal.toString());
                   curVal = new StringBuffer();
                   inval = false;
                }
                else if (ch == '\r')
                {
                   // skip
                }
                else if (ch == '\n')
                {
                   break;
                }
                else
                {
                   curVal.append((char) ch);
                }
             }
             ch = r.read();
          }

          res.add(curVal.toString());
          return res;
       }

       /**
        * The method disables validation of SSL certificates when initiating HTTPS client connections
        * using java.net facility. The method creates new SSL Socket Factory and Host Name Verifier and
        * sets them as the defaults in {@linkplain HttpsURLConnection}.
        * <p>
        * All connections initiated with {@linkplain HttpsURLConnection} will be made without certificate
        * validations! Do not use on production systems!
        */
       public static void disableClientSSLCertificateValidation()
       {
          // do not validate certificate chains
          TrustManager[] trustManager = new TrustManager[]
          {
             new X509TrustManager()
             {
                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers()
                {
                   return null;
                }

                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType)
                {
                }
             }
          };

          try
          {
             // install socket factory accepting all certificates
             SSLContext sc = SSLContext.getInstance("SSL");
             sc.init(null, trustManager, new SecureRandom());
             HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

             // accept all hosts
             HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);

             LOG.log(Level.WARNING, "!!! Disabled SSL certificate validation for outgoing HTTPS connections. " +
                                    "This is intended for test environments only !!! ");
          }
          catch (Exception ex)
          {
             LOG.log(Level.SEVERE, "Failure during HttpsURLConnection setup!", ex);
          }
       }

       /**
        * Poll all elements of a Queue and return them in the same order in a List. Useful when the elements
        * have to be returned and the Queue cleared without losing any newly added elements.
        *
        * @param   queue
        *          A queue of T elements.
        * @param   <T>
        *          The type of elements in the Queue.
        *
        * @return  Empty Optional when the queue is empty or Optional of List with all the elements in the queue.
        */
       public static <T> Optional<List<T>> pollAll(Queue<T> queue)
       {
          if (queue == null || queue.isEmpty())
          {
             return Optional.empty();
          }
          List<T> elements = new ArrayList<>(queue.size());
          while (!queue.isEmpty())
          {
             elements.add(queue.poll());
          }
          return Optional.of(elements);
       }

       /**
        * Get a simplified string which describes the current context. This is
        * intended primarily for naming a thread.
        *
        * @param    descr
        *           Description of the thread's purpose.
        *
        * @return   Simple description of context.
        */
       public static String createThreadName(String descr)
       {
          SecurityManager sm  = SecurityManager.getInstance();
          String          txt = null;

          if (sm != null && SecurityManager.initialized() && sm.contextSm.hasContext())
          {
             txt = String.format("%s [%08X:%s]",
                                 descr,
                                 sm.sessionSm.getSessionId(),
                                 sm.getUserId());
          }

          return txt;
       }

       /**
        * Returns the configured legacy root package as a path on the classpath (starts and ends with Unix style
        * file separator "/").
        *
        * @return   See above.
        */
       public static String getLegacyPkgRootAsJarPath()
       {
          String packagePath = packageToJarPath(SourceNameMapper.getPackageRoot());
          if (!packagePath.startsWith("/"))
          {
             packagePath = "/" + packagePath;
          }
          return packagePath;
       }

       /**
        * Returns the java package name as a path on the classpath (starts and ends with Unix style file
        * separator "/").
        *
        * @param   packageName
        *          The package name.
        *
        * @return   See above.
        */
       public static String packageToJarPath(String packageName)
       {
          String packagePath = packageName.replace('.', '/');
          if (!packagePath.endsWith("/"))
          {
             packagePath = packagePath + "/";
          }
          return packagePath;
       }

       /**
        * Logs the error stream for the process.
        *
        * @param   logger
        *          The logger.
        * @param   process
        *          The process.
        * @param   processIdentifier
        *          The identifier of the process, used for the log.
        */
       public static void logProcessError(CentralLogger logger, Process process, String processIdentifier)
       {
          try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())))
          {
             List<String> lines = new ArrayList<>();
             String line;
             // without ready check, sometimes the read freezes
             while (errorReader.ready() && (line = errorReader.readLine()) != null)
             {
                lines.add(line);
             }
             String error = String.join("", lines);
             if (StringHelper.hasContent(error))
             {
                logger.warning("Process for '" + processIdentifier + "' returned error: " + error);
             }
          }
          catch (Throwable t)
          {
             logger.warning("Process error stream for '" + processIdentifier + "' can't be handled.", t);
          }
       }

       /**
        * Collect all the implemented interfaces (directly and from the hierarchy), for the given
        * class.
        * <p>
        * The collection is performed doing a BFS walk of the hierarchy, starting from the given class.
        *
        * @param    cls
        *           The class from where to get the interfaces.
        * @param    interfaces
        *           A set where to store the interfaces, in the BFS order.
        * @param    collected
        *           A set of interfaces already processed.
        */
       private static void collectInterfacesImpl(Class<?> cls, Set<Class<?>> interfaces, Set<Class<?>> collected)
       {
          if (collected.contains(cls))
          {
             return;
          }

          Class<?>[] ifcs = cls.getInterfaces();
          if (ifcs != null)
          {
             for (Class<?> ifc : ifcs)
             {
                interfaces.add(ifc);
             }

             for (Class<?> ifc : ifcs)
             {
                collectInterfacesImpl(ifc, interfaces, collected);

                collected.add(ifc);
             }
          }

          if (cls.getSuperclass() != null)
          {
             collectInterfacesImpl(cls.getSuperclass(), interfaces, collected);
          }
       }

       /**
        * A method that returns the pathCache, if already initialized,
        * otherwise, initializes it.
        *
        * @return   See above.
        */
       private static ExpiryCache<String, String> getPathCache()
       {
          if (pathCache == null)
          {
             pathCache = CacheManager.createLRUCache(Utils.class, "pathCache", 131072);
          }
          return pathCache;
       }

       /**
        *
        * Utility class extending HashMap functionality
        *
        * @param <K>
        *        class of the key
        * @param <V>
        *        class of the value
        */
       public static class InstrumentedMap<K,V> extends HashMap<K,V>
       {
          /** the value supplier */
          private Supplier<V> valueSupplier;

          /**
           * Constructor
           *
           * @param valueSupplier
           *        the value supplier
           */
          public InstrumentedMap(Supplier<V> valueSupplier)
          {
             super();
             this.valueSupplier = valueSupplier;
          }

          /**
           * Modify the value of a map creating it if absent
           *
           * @param key
           *        the key
           * @param modifier
           *        the value modifier
           *
           * @return the value
           */
          public V modifyValue(K key, Consumer<V> modifier)
          {
             V value = get(key);
             if (value == null)
             {
                put(key, value = valueSupplier.get());
             }
             modifier.accept(value);
             return value;
          }
       }

       /**
        * Stores global data relating to the state of the current context.
        */
       private static class WorkArea
       {
          /** Internal counter tha last used value */
          private int counter = -1;

          /**
           * Project token. This is used for per-project directory settings in case multiple projects
           * are 'parked' within the same P2J installation and setting are different.
           */
          private String projectToken = null;

          /** The explicitly set temporary directory coming from the StartupParameters */
          private String temporaryDirectory = null;

          /** The name of the FWD client's temporary folder. */
          private String defaultFwdTempFolder = "fwd" + new Random().nextLong();
       }

       /**
        * Simple container that stores and returns a context-local instance of
        * the global work area.
        */
       private static class ContextContainer
       extends ContextLocal<WorkArea>
       {
          /**
           * Initializes the work area, the first time it is requested within a
           * new context.
           *
           * @return   The newly instantiated work area.
           */
          @Override
          protected WorkArea initialValue()
          {
             WorkArea wa = new WorkArea();
             return wa;
          }
       }

       /**
        * Auxiliary class for use on the normalizeOrder method
        */
       private static class ZKey implements Comparable<ZKey>
       {
          /** Z-order class of the widget */
          private final ZOrderClass cls;

          /** initial position of the widget */
          private final int idx;

          /**
           * Constructor
           *
           * @param cls
           *           Z-order class of the widget
           * @param idx
           *           initial position of the widget
           */
          public ZKey(ZOrderClass cls, int idx)
          {
             super();
             this.cls = cls;
             this.idx = idx;
          }

          /**
           * The implementation of the {@link java.lang.Comparable#compareTo} method
           */
          @Override
          public int compareTo(ZKey other)
          {
             return this.cls.compareTo(other.cls) == 0 ?
                    this.idx - other.idx :
                    -this.cls.compareTo(other.cls);
          }
       }

       /**
        * Global ReflectASM data for {@link Utils#invoke(Method, Object, Object...)}.
        */
       private static class MethodData
       {
          /** The ReflectASM method access class. */
          private final MethodAccess access;

          /** Cache of method indices for each {@link Method} instance. */
          private final Map<Method, Integer> methodAccessIdx;

          /***
           * Create a new instance and populate the method access index.
           *
           * @param    cls
           *           The type which needs to be initialized and cached.
           */
          public MethodData(Class<?> cls)
          {
             this.access = MethodAccess.get(cls);

             Map<Method, Integer> midx = new HashMap<>();
             String[] mnames = access.getMethodNames();
             Class<?>[][] mpars = access.getParameterTypes();
             for (int i = 0; i < mnames.length; i++)
             {
                try
                {
                   Method method = null;
                   Class<?> type = cls;
                   do
                   {
                      method = cls.getDeclaredMethod(mnames[i], mpars[i]);
                      type = type.getSuperclass();
                   }
                   while (method == null);

                   midx.put(method, access.getIndex(method.getName(), method.getParameterTypes()));
                }
                catch (NoSuchMethodException |
                       SecurityException e)
                {
                   // ignore
                }
             }

             methodAccessIdx = midx;
          }
       }
    }