@SuppressWarnings("unchecked") // we checked the type of each use so we know the casts are safe
	public static <T> List<Class<? extends T>> getClasses(Class<T> ancestorClass,
			Predicate<Class<? extends T>> classFilter) {
		if (!hasSearched) {
			return List.of();
		}
		if (isSearching) {
			throw new IllegalStateException(
				"Cannot call the getClasses() while the ClassSearcher is searching!");
		}

		String suffix = getExtensionPointSuffix(ancestorClass.getName());
		if (suffix == null) {
			return List.of();
		}

		List<Class<? extends T>> list = new ArrayList<>();
		for (ClassFileInfo info : extensionPointSuffixToInfoMap.getOrDefault(suffix, Set.of())) {

			if (falsePositiveCache.contains(info)) {
				continue;
			}

			Class<?> c = loadedCache.get(info);
			if (c == null) {
				c = loadExtensionPoint(info.path(), info.name());
				if (c == null) {
					falsePositiveCache.add(info);
					continue;
				}
			}

			loadedCache.put(info, c);

			if (ancestorClass.isAssignableFrom(c) &&
				(classFilter == null || classFilter.test((Class<T>) c))) {
				list.add((Class<? extends T>) c);
			}
		}

		prioritizeClasses(list);
		return list;
	}
Get {@link ExtensionPointProperties#priority() priority-sorted} classes that
implement or derive from the given ancestor class

@param ancestorClass the ancestor class
@param classFilter A Predicate that tests class objects (that are already of type T)
			for further filtering, {@code null} is equivalent to "return true"
@return {@link ExtensionPointProperties#priority() priority-sorted} list of
			classes that implement or extend T and pass the filtering test performed by the
			predicate
Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearcher.java