docs: private static boolean match(String string, String pattern) {
  		if (string == null || string.isEmpty()) {
  			return false;
  		}
  		String[] wcs = tokenize(pattern);
  
  		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++;
  					anyChars = false;
  
  				} else if (wcs[wcsIdx].equals("*")) {
  					// set any chars status
  					anyChars = true;
  					if (wcsIdx == wcs.length - 1) {
  						textIdx = string.length();
  					}
  
  				} else {
  					// matching text token
  					if (anyChars) {
  						// any chars then try to locate text token
  						textIdx = string.indexOf(wcs[wcsIdx], textIdx);
  						if (textIdx == -1) {
  							// token not found
  							break;
  						}
  						int repeat = string.indexOf(wcs[wcsIdx], textIdx + 1);
  						if (repeat >= 0) {
  							backtrack.push(new int[] { wcsIdx, repeat });
  						}
  					} else {
  						// matching from current position
  						if (!string.startsWith(wcs[wcsIdx], textIdx)) {
  							// 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 == string.length()) {
  				return true;
  			}
  
  		} while (backtrack.size() > 0);
  
  		return false;
  	}