Monday, November 29, 2010

Interrogating the Java Model in an Eclipse JDT Plugin to find a method

Previously I noted how to setup an Eclipse Plugin that can access JDT, noting some nomenclature and setup steps that are non-intuitive to an Eclipse noob. In trying to wire up an in-Eclipse version of the JSP checker (see post), one of the first problems encountered is that finding methods using java.lang.reflect doesn't work; we need to use the JDT model to ask questions about the users Java code instead.

As with most noob problems this proves fairly simple once done, but initially the Eclipse APIs provide a nice baffling wall. Our code is still running in a org.eclipse.core.resources.IncrementalProjectBuilder derived class and thus has a reference to the IResource for the file we are interested in (in my specific example, for a JSP file that in turn has a qualified class name in it). To implement the JSP check we'll need to then search for a user class by qualified type name, then check if the class has a specific method.

Our first step is to check if the type exists. Initially we only want to look in the current project, so we'll need a reference to the IJavaElement for the project. It turns out we can get a reference to this from JavaCore by passing in the IProject, which we can get from our IResource for the file. This means checking if a type exists can be done similar to the following (given that we have IResource resource as the resource our builder is working on and String qualifiedTypeName containing the type name of interest):
...
IJavaProject javaProject = JavaCore.create(resource.getProject());
IType type;
//normally findType returns null if there is no such type but theoretically it can throw JavaModelException also
try {
  type = javaProject.findType(qualifiedTypeName);
} catch (JavaModelException jme) {
  throw new RuntimeException("Error trying to find type \"" + typeName + "\"");
}
boolean typeExists = null != type;
Wonderous! Now to see if we can look for a method. The docs seem to suggest we can use the SearchEngine to do look for stuff in Java code, passing a suitable SearchPattern in. We also have to provide a SearchRequestor implementation to trap our results, though in our case all we want to do is count them. For the time being we'll just look for 0 or >0 though eventually we'll have to determine whats going on if there are 2+ matches. We'll wrap all this into a couple of classes to help us simplify coding against the Java model as most of our code really only cares if something is a valid type and whether a JavaBeans-style property exists, not how we figure that out. Here is draft 1 of the Eclipse class to aid us in figuring out if typenames and properties are valid:
package com.example.validator;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;

import com.active.commons.StringUtils;

public class JavaClassInfo {
 private IJavaProject javaProject;
 private IType type;
 
 public JavaClassInfo(IProject project, String typeName) {
  javaProject = JavaCore.create(project);  
  try {
   type = javaProject.findType(typeName);
  } catch (JavaModelException jme) {
   throw new RuntimeException("Error trying to find type \"" + typeName + "\"");
  }    
 }

 public boolean hasProperty(String propertyName) {
  return hasMethod("get" + StringUtils.capitalize(propertyName))
   || hasMethod("is" + StringUtils.capitalize(propertyName));
 }
 
 private boolean hasMethod(String methodName) {
  //Guard: must be a valid type to have methods
  if (!isValidTypename()) {
   return false;
  }
  
  SearchPattern pattern = SearchPattern.createPattern(
    methodName, 
    IJavaSearchConstants.METHOD, 
    IJavaSearchConstants.DECLARATIONS, 
    SearchPattern.R_EXACT_MATCH);
  
  IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[] { type });
  
  CountingSearchRequestor matchCounter = new CountingSearchRequestor();
  
  SearchEngine search = new SearchEngine();
  try {
   search.search(pattern, 
    new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }, 
    scope, 
    matchCounter, 
    null);
  } catch (CoreException ce) {
   System.out.println("Couldn't find type: " + ce.getMessage());
   ce.printStackTrace();
  } 
  
  if (matchCounter.getNumMatch() > 1) {
   System.out.println("Bit weird; " + matchCounter.getNumMatch() + " matches for " + methodName);
  }
  
  return matchCounter.getNumMatch() > 0;
 }

 public boolean isValidTypename() {
  return type != null;  
 }
}
Note that the following implementation of SearchRequestor is used to count results:
package com.example.validator;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchRequestor;

public class CountingSearchRequestor extends SearchRequestor {
 private int numMatch;
 
 @Override
 public void acceptSearchMatch(SearchMatch match) throws CoreException {
  numMatch++;
 }

 public int getNumMatch() {
  return numMatch;
 }
}

This will allow us to write code similar to the following to check on type/method existance:
JavaClassInfo classInfo = new JavaClassInfo(resource.getProject(), qualifiedTypeName);
...
if (!classInfo.hasProperty(propertyName)) {
   //add a Marker discussing how dumb you are to have used a property that doesn't exist!
}
One step closer to having our in-Eclipse JSP checker.

1 comment:

Post a Comment