I ended up implementing it in the following way. It does require a bit of recursion in order to follow all the method signatures of a type.
// the type to analyze
IType typeToAnalyze = ...
// create an ASTParser
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
// set the resolution to a specific project which has the full classpath
parser.setProject(aJavaProject);
// configure the parser to resolve bindings
parser.setResolveBindings(true);
// I'm ok with ignoring method bodies
parser.setIgnoreMethodBodies(true);
// get the IBinding from the parser
IBinding[] bindings = parser.createBindings(new IJavaElement[] { typeToAnalyze }, monitor);
// I'm interested in the jar files or IJavaProjects
Set<IPath> dependencies = new HashSet<>();
// collect processed type names in a set to avoid endless recursion
Set<String> processedTypes = new HashSet<>();
// because we passed in an IType the returned binding is an instance of ITypeBinding
for (final IBinding binding : bindings) {
collectDependenciesFromType((ITypeBinding) binding, dependencies, processedTypes, monitor);
}
With a couple supporting methods:
private void collectDependenciesFromType(final ITypeBinding binding, final Set<IPath> dependencies,
final Set<String> processedTypes, final IProgressMonitor monitor) {
if (monitor.isCanceled())
throw new OperationCanceledException();
if (binding.isAnonymous() || binding.isPrimitive() || binding.isNullType() || binding.isGenericType()
|| binding.isTypeVariable() || binding.isCapture() || binding.isWildcardType() || binding.isRecovered())
return;
if (binding.isArray()) {
collectDependenciesFromType(binding.getElementType(), dependencies, processedTypes, monitor);
return;
}
if (binding.getPackage().getName().startsWith("java."))
return;
if (processedTypes.contains(binding.getQualifiedName()))
return;
processedTypes.add(binding.getQualifiedName());
monitor.subTask(binding.getQualifiedName());
final IPath path = getJarOrProjectPath((IType) binding.getJavaElement());
if (path == null)
return;
dependencies.add(path);
for (final ITypeBinding interfaceBinding : binding.getInterfaces()) {
collectDependenciesFromType(interfaceBinding, dependencies, processedTypes, monitor);
}
final ITypeBinding superclass = binding.getSuperclass();
if (superclass != null) {
collectDependenciesFromType(superclass, dependencies, processedTypes, monitor);
}
for (final ITypeBinding typeParameter : binding.getTypeParameters()) {
collectDependenciesFromType(typeParameter, dependencies, processedTypes, monitor);
}
for (final IMethodBinding methodBinding : binding.getDeclaredMethods()) {
collectDependenciesFromMethod(methodBinding, dependencies, processedTypes, monitor);
}
}
private void collectDependenciesFromMethod(final IMethodBinding binding, final Set<IPath> dependencies,
final Set<String> processedTypes, final IProgressMonitor monitor) {
if (monitor.isCanceled())
throw new OperationCanceledException();
if (Modifier.isPrivate(binding.getModifiers()) || Modifier.isDefault(binding.getModifiers()))
return;
for (final ITypeBinding parameterType : binding.getParameterTypes()) {
collectDependenciesFromType(parameterType, dependencies, processedTypes, monitor);
}
collectDependenciesFromType(binding.getReturnType(), dependencies, processedTypes, monitor);
}
private IPath getJarOrProjectPath(final IType type) {
if (type.isBinary()) {
final IPackageFragmentRoot root = (IPackageFragmentRoot) type
.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
if ((root != null) && root.isArchive() && "jar".equals(root.getPath().getFileExtension()))
return root.getPath();
} else {
final IJavaProject javaProject = type.getJavaProject();
if (javaProject != null)
return javaProject.getPath();
}
return null;
}