//
  // Returns true if this method is allowed to raise SQLFeatureNotSupportedException.
  //
  private boolean isExcludable(Method method) throws Exception {
    Class iface = method.getDeclaringClass();
    HashSet<Method> excludableMethods = excludableMap.get(iface);

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

    return excludableMethods.contains(method);
  }
  //
  // Initialize the hashtable of methods which are allowed to raise
  // SQLFeatureNotSupportedException.
  //
  private void initializeExcludableMap(HashSet<String> vanishedMethodList) throws Exception {
    excludableMap = new Hashtable<Class, HashSet<Method>>();

    int count = rawExcludables.length;

    for (int i = 0; i < count; i++) {
      Exclusions exclusions = rawExcludables[i];
      Class<?> iface = exclusions.getInterface();
      MD[] mds = exclusions.getExcludedMethods();
      int exclusionCount = mds.length;
      HashSet<Method> excludedMethodSet = new HashSet<Method>();

      for (int j = 0; j < exclusionCount; j++) {
        MD md = mds[j];

        if (!md.requiredAtThisLevel()) {
          continue;
        }

        //
        // If we are strictly enforcing the JDBC standard,
        // then expose the mandatory methods which we know Derby
        // doesn't implement.
        //
        if (STRICT_ENFORCEMENT && !md.isOptional()) {
          continue;
        }

        Method method = null;

        try {
          method = iface.getMethod(md.getMethodName(), md.getArgTypes());
        } catch (NoSuchMethodException e) {
        }

        if (method == null) {
          vanishedMethodList.add(
              "Method has vanished from SQL interface: " + iface.getName() + "." + md);
        }

        excludedMethodSet.add(method);
      }

      excludableMap.put(iface, excludedMethodSet);
    }
  }
 //
 // Record an unexpected error.
 //
 private void recordUnexpectedError(
     Object candidate,
     Class iface,
     Method method,
     HashSet<String> notUnderstoodList,
     Throwable cause)
     throws Exception {
   notUnderstoodList.add(candidate.getClass().getName() + " " + method + " raises " + cause);
 }
  // debug print the list of methods which have disappeared from the SQL interface
  private void printVanishedMethodList(HashSet<String> vanishedMethodList) {
    int count = vanishedMethodList.size();

    if (count == 0) {
      return;
    }

    println("--------------- VANISHED METHODS ------------------");
    println("--");

    String[] result = new String[count];

    vanishedMethodList.toArray(result);
    Arrays.sort(result);

    for (int i = 0; i < count; i++) {
      println(result[i]);
    }
  }
  // debug print the list of methods which throw SQLFeatureNotSupportedException
  private void printUnsupportedList(HashSet<String> unsupportedList) {
    int count = unsupportedList.size();

    if (count == 0) {
      return;
    }

    println("--------------- UNSUPPORTED METHODS ------------------");
    println("--");

    String[] result = new String[count];

    unsupportedList.toArray(result);
    Arrays.sort(result);

    for (int i = 0; i < count; i++) {
      println(result[i]);
    }
  }
  // Debug print the list of method failures which we don't understand
  private void printNotUnderstoodList(HashSet<String> notUnderstoodList) {
    int count = notUnderstoodList.size();

    if (count == 0) {
      return;
    }

    println("\n\n");
    println("--------------- NOT UNDERSTOOD METHODS ------------------");
    println("--");

    String[] result = new String[count];

    notUnderstoodList.toArray(result);
    Arrays.sort(result);

    for (int i = 0; i < count; i++) {
      println(result[i]);
    }
  }
  //
  // Examine a single method to see if it raises SQLFeatureNotSupportedException.
  //
  private void vetMethod(
      Object candidate,
      Class iface,
      Method method,
      HashSet<String> unsupportedList,
      HashSet<String> notUnderstoodList)
      throws Exception {
    try {
      method.invoke(candidate, getNullArguments(method.getParameterTypes()));

      // it's ok for the method to succeed
    } catch (Throwable e) {
      if (!(e instanceof InvocationTargetException)) {
        recordUnexpectedError(candidate, iface, method, notUnderstoodList, e);
      } else {
        Throwable cause = e.getCause();

        if (cause instanceof SQLFeatureNotSupportedException) {
          boolean isExcludable = isExcludable(method);

          if (!isExcludable) {
            StackTraceElement[] stack = cause.getStackTrace();
            int i = 0;
            while (i < stack.length && !stack[i].getMethodName().equals("notImplemented")) {
              ++i;
            }
            while (i < stack.length && stack[i].getMethodName().equals("notImplemented")) {
              ++i;
            }
            if (i == stack.length) {
              // cause.printStackTrace();
            }

            unsupportedList.add(
                candidate.getClass().getName()
                    + ": "
                    + method
                    + "@"
                    + (i == stack.length ? "no source" : cause.getStackTrace()[i]));
          } else {

          }
        } else if (cause instanceof SQLException) {
          // swallow other SQLExceptions, caused by bogus args
        } else if (cause instanceof NullPointerException) {
          // swallow other NPEs, caused by bogus args
        } else if (cause instanceof ArrayIndexOutOfBoundsException) {
          // swallow these, caused by bogus args
        } else {
          recordUnexpectedError(candidate, iface, method, notUnderstoodList, cause);
        }
      }
    }
  }
  /** Find all methods in this framework which raise SQLFeatureNotSupportedException. */
  public void testSupportedMethods() throws Exception {
    getTestConfiguration().setVerbosity(true);

    HashSet<String> vanishedMethodList = new HashSet<String>();
    HashSet<String> unsupportedList = new HashSet<String>();
    HashSet<String> notUnderstoodList = new HashSet<String>();

    // Build map of interfaces to their methods which may raise SQLFeatureNotSupportedException.
    initializeExcludableMap(vanishedMethodList);

    vetDataSource(unsupportedList, notUnderstoodList);
    vetConnectionPooledDataSource(unsupportedList, notUnderstoodList);
    vetXADataSource(unsupportedList, notUnderstoodList);

    //
    // Print methods which behave unexpectedly.
    //
    printVanishedMethodList(vanishedMethodList);
    printUnsupportedList(unsupportedList);
    printNotUnderstoodList(notUnderstoodList);

    int actualErrorCount =
        vanishedMethodList.size() + unsupportedList.size() + notUnderstoodList.size();

    assertEquals("Unexpected discrepancies.", 0, actualErrorCount);
  }