docs: static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity)
      {
          if (filename == null && wildcardMatcher == null)
          {
              return true;
          }
          if (filename == null || wildcardMatcher == null)
          {
              return false;
          }
          if (caseSensitivity == null)
          {
              caseSensitivity = IOCase.SENSITIVE;
          }
          String[] wcs = splitOnTokens(wildcardMatcher);
          boolean anyChars = false;
          int textIdx = 0;
          int wcsIdx = 0;
          Stack<int[]> backtrack = new Stack<int[]>();
  
          // loop around a backtrack stack, to handle complex * matching
          do
          {
              if (backtrack.size() > 0)
              {
                  int[] array = backtrack.pop();
                  wcsIdx = array[0];
                  textIdx = array[1];
                  anyChars = true;
              }
  
              // loop whilst tokens and text left to process
              while (wcsIdx < wcs.length)
              {
  
                  if (wcs[wcsIdx].equals("?"))
                  {
                      // ? so move to next text char
                      textIdx++;
                      if (textIdx > filename.length())
                      {
                          break;
                      }
                      anyChars = false;
  
                  }
                  else if (wcs[wcsIdx].equals("*"))
                  {
                      // set any chars status
                      anyChars = true;
                      if (wcsIdx == wcs.length - 1)
                      {
                          textIdx = filename.length();
                      }
  
                  }
                  else
                  {
                      // matching text token
                      if (anyChars)
                      {
                          // any chars then try to locate text token
                          textIdx = caseSensitivity.checkIndexOf(filename, textIdx, wcs[wcsIdx]);
                          if (textIdx == -1)
                          {
                              // token not found
                              break;
                          }
                          int repeat = caseSensitivity.checkIndexOf(filename, textIdx + 1, wcs[wcsIdx]);
                          if (repeat >= 0)
                          {
                              backtrack.push(new int[]
                                      {
                                          wcsIdx, repeat
                                      });
                          }
                      }
                      else
                      {
                          // matching from current position
                          if (!caseSensitivity.checkRegionMatches(filename, textIdx, wcs[wcsIdx]))
                          {
                              // couldnt match token
                              break;
                          }
                      }
  
                      // matched text token, move text index to end of matched token
                      textIdx += wcs[wcsIdx].length();
                      anyChars = false;
                  }
  
                  wcsIdx++;
              }
  
              // full match
              if (wcsIdx == wcs.length && textIdx == filename.length())
              {
                  return true;
              }
  
          } while (backtrack.size() > 0);
  
          return false;
      }