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;
      }
   }
}