How to discover all dependencies (aka. "imports") from an IType?

62 Views Asked by At

I'm using the Eclipse JDT API and I have an IType. I'd like to discover all "dependencies" of this IType.

I think this would be:

  • direct imports (import statements)
  • as well full qualifier usage within the method signatures
  • above information from supertypes

I am ignoring runtime dependency discovery right now. My goal is to come up with a QuickFix for "indirectly referenced from class file" for a setup with strict classpaths (no transitive dependencies).

One more complication: the type itself or supertypes could come from a jar without source attachment.

I'll probably need to produce the AST for this as this answer recommends. Any tips for the approach? Do I need to walk the full type? Is there another way to just get all the bindings and use them? Would that work?

1

There are 1 best solutions below

0
Gunnar On

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;
    }