Showing posts with label Groovy. Show all posts
Showing posts with label Groovy. Show all posts

Wednesday, June 29, 2011

Groovy-Eclipse 2.5.1 Released

The Groovy-Eclipse team is proud to release Groovy-Eclipse 2.5.1. This release is now available for install using the update sites here:

for Eclipse Indigo:
http://dist.springsource.org/release/GRECLIPSE/e3.7/

for Eclipse Helios:
http://dist.springsource.org/release/GRECLIPSE/e3.6/

In this release, we have continued to work on our DSLD support (and we
ship with some useful DSLD scripts). Additionally, we have a GA
release of the groovy-eclipse-compiler-plugin for Maven. Now that
Indigo has been released, the 3.7 stream of Groovy-Eclipse will become
the main development branch. For now, we will continue to support the
3.6 stream of Groovy-Eclipse and will put out at least one more
release.

You can find all the details at our New & Noteworthy page.

Please send feedback here or raise an issue on our issuetracker.

Friday, June 24, 2011

Vancouver Eclipse Demo Camp 2011

In honor of the Indigo release of Eclipse, I will be hosting the annual Eclipse Demo camp in Vancouver on Monday, June 27. The demo camp is co-sponsored by the Eclipse Foundation, VMWare, and Tasktop.

We're expecting a full house as well as lots of exciting talks, including one on Groovy/Grails support in Eclipse and the SpringSource Tool Suite, a talk from Tasktop, and a talk about the Eclipse Modelling Framework from Ed Merks, the project lead.

If you are in the Vancouver area and interested in attending, please add your name to the wiki:

http://wiki.eclipse.org/index.php?title=Eclipse_DemoCamps_Indigo_2011/Vancouver

Alternatively, contact me directly (or on this blog) and I'll add you myself. Hope to see you there!

Monday, September 6, 2010

Where are all my stubs?



Update: you must also include a pluginRepositories section. See below for XML snippet.



Update: See here for a sample project and the source code of the compiler integration.




The standard way of compiling joint Groovy-Java code outside of Eclipse has always been through the use of stubs:

  1. Generate Java stub files for the Groovy files
  2. Compile the Java files using the stubs to compile against
  3. Compile the Groovy files

Although this works reasonably well in many situations, there are some complications and problems with this approach, most of which have already been described recently in detail on the groovy-dev mailing list here and here, so I won't go into them in this post.

About a year ago, we introduced Groovy-Eclipse 2.0, which compiles Groovy code by plugging into the JDT compiler and does not need to generate stub files. And as Andy Clement describes, it is possible to run the compiler in batch mode on the command line.

And now, with a little bit of glue code required, I have released a snapshot of the compiler with both ant and maven integration. Although, this is still early work, I do hope that this approach will solve many of the problems that Groovy programmers are having with stub generation. I'll describe below how they both work.

Ant integration for Groovy-Eclipse


Ant integration for the batch compiler is fairly simple.

  1. Download the groovy-eclipse-batch-0.5.0.jar from its temporary location.
  2. Add this jar to your ~/.ant/lib directory.
  3. Once you have that, you need to set the build.compiler property to org.codehaus.groovy.eclipse.ant.GroovyCompilerAdapter.

This will cause ant's javac task to delegate the Groovy-Eclipse compiler for the actual compilation. This means that it is possible to pass any combination of Groovy and Java files to the compiler and most parameters applicable for javac are still available when using the compiler adapter.

A very simple script that uses the Groovy compiler adapter looks like this:

<target name="compile">
  <property name="build.compiler"
           value="org.codehaus.groovy.eclipse.ant.GroovyCompilerAdapter">
  <javac srcdir="src" destdir="bin"/>
</target>

This script sets compiler adapter and compiles all source files in src, placing the resulting class files in bin. Both *.java files and *.groovy files are included in the compilation.

Maven integration for Groovy-Eclipse


Groovy-Eclipse can now also be used from maven. To do so, add the following to your pom.xml.

The artifacts are currently in the SpringSource snapshot maven repo. You must add it as a regular repository:

<repositories>
  <repository>
  <id>springsource</id>
  <url>http://maven.springframework.org/snapshot</url>
  <releases><enabled>true</enabled></releases>
  <snapshots><enabled>true</enabled></snapshots>
  </repository>
</repositories>

as well as a plugin repository:

<pluginRepositories>
  <pluginRepository>
  <id>springsource</id>
  <url>http://maven.springframework.org/snapshot</url>
  </pluginRepository>
</pluginRepositories>

And in your plugin section, you must change the compiler used by the maven-compiler-plugin. Like the javac ant task, the maven-compiler-plugin does not actually compile, but rather delegates the compilation to a different artifact:

<build>
...
<plugins>
  <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.3.1</version>
    <configuration>
      <compilerId>groovy-eclipse-compiler</compilerId>
      <verbose>true</verbose>
    </configuration>
    <dependencies>
      <dependency>
        <groupId>org.codehaus.groovy</groupId>
        <artifactId>groovy-eclipse-compiler</artifactId>
        <version>0.0.1-SNAPSHOT</version>
      </dependency>
    </dependencies>
  </plugin>
  ...
</plugins>
</build>

This will allow Groovy files to be compiled. The maven-compiler-plugin prefers all source files to be in src/main/java and src/test/java, but if you prefer you can use the standard Groovy convention and keep your files in src/main/groovy and src/test/groovy. You can do so by adding the following plugin to your build section of the pom:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>1.5</version>
  <executions>
    <execution>
      <id>add-source</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>add-source</goal>
      </goals>
      <configuration>
        <sources>
          src/main/groovy
          src/test/groovy
        </sources> 
      </configuration>
    </execution>
  </executions>
</plugin>

This approach is still in an alpha state and has not been widely tested. It was hard to find reasonably large Groovy-Java projects that use maven for me to try this on. The largest project I have compiled in this way is the GPars project (GPars uses gradle for its build, but I adapted its build.gradle to a pom.xml and successfully ran maven on it). This project includes 168 Java and Groovy files in main as well as 338 Groovy files in test. In a not particularly scientific manner, I did a few runs of building the main and test classes using both Groovy-Eclipse and GMaven and the results are that Groovy-Eclipse is reasonably faster than GMaven for this project:

  • Time to compile main and test classes using GMaven: 36s
  • Time to compile main and test classes using Groovy-Eclipse: 28s

In addition to being largely untested in the wild, there are a few caveats when using Groovy-Eclipse:

  • Since stubs are not generated, GroovyDoc and any other artifacts that rely on stubs cannot be generated.
  • This only supports Groovy 1.7.
  • Third (ant only), your project must have at least one Java file in it (this can be an empty stub), or else ant will finish without compiling anything. There is a patch for this (Bug 48829), but I am waiting for it to be contributed back to ant.
  • Fourth (maven only), your maven project must have at least one groovy file or else compilation will not occur. (Though, if your project doesn't have any Groovy files, then why are you using a Groovy compiler?)

There is still some work to be done, but it is ready enough for people to start trying it out. Feedback is greatly appreciated. You can reply to this blog post, send a message to the mailing list, or raise an issue on jira.

Saturday, August 7, 2010

Groovy-PDE Redux

Up until recently, doing any PDE work with Groovy has been a bit of a kludge. First, you had to create a customCallBack.xml script. Inside this script you had to call out to a special groovy.jdtcompile ant task, using a magic set of classpath references. When this approach worked, it did so by first compiling your Java code (with errors of course since your groovy code is not touched), and then re-compiling all your code using the joint compiler provided by Groovy-Eclipse.

Not so pretty. It works for Groovy-Eclipse, but that is only because I know exactly what its limitations are and how to work around them.

A short while ago, I wrote about how we re-implemented PDE build for projects that use the AspectJ compiler. I've recently done the same for plugin projects that use Groovy.

Here's how it works:

  1. Install the latest dev snapshot of Groovy-Eclipse for Helios (you can write plugins that target Galileo (Eclipse 3.5), but you must use the Helios PDE builder to create the plugins).
  2. For each of your plugin projects, add the following to your build.properties file:
    sourceFileExtensions=*.java, *.groovy
    compilerAdapter=org.codehaus.groovy.eclipse.ant.GroovyCompilerAdapter
    compilerAdapter.useLog=true  # this ensures that exceptions are logged to the proper log file.
  3. Now you can run your PDE export (either headless or from within a running Eclipse using one of the Export wizards).

The only caveat is that each plugin project must contain at least one .java file or else the PDE builder will ignore that plugin. The problem is described in Bug 318951. Before this can be addressed, I need a patch committed to the javac task, described here: Apache Bug 48829 (please vote the bug up if you want to see this fixed!).

With that, creating Groovy-based Eclipse plugins now requires much, much less black magic.

Saturday, May 15, 2010

Extending Groovy-Eclipse for use with Domain-Specific Languages Part II

A while ago, I wrote an article about how to extend Groovy-Eclipse for domain specific languages. It was always my intention to write a follow-up that included an example project. Thanks to some prodding on the mailing list, here it is, 5 months later. I finally have an example Groovy-Eclipse extension project available.

The code mostly speaks for itself, so if you are interested in how to extend Groovy-Eclipse, first read part I of this article, and then download the example project.

Here is the example plugin project that extends Groovy-Eclipse. Import this plugin into your Eclipse development workspace.

And here is a simple runtime project that uses all of the extensions provided in the plugin project. Import this project into your Eclipse runtime workspace. If you want to create your own project that uses this example, be sure to add the following project nature to your .project file:
org.codehaus.groovy.eclipse.example.nature
Once again, the code speaks for itself, so there is nothing more that I need to say about it. Enjoy!

Monday, April 12, 2010

Groovy-Eclipse for Helios

Today we released our first snapshot of a Groovy-Eclipse build for Eclipse 3.6 (Helios). We haven't set the continuous build system up yet, so for now if you are looking to try it out, you can download the zip here:

http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.6/groovy-update-site-e36.zip

And then point your p2 installer to the downloaded archive. You will then be able to install Groovy-Eclipse. We have only tested on 3.6 M6 and we will continue to ensure that it works on later milestones as they come out.

As always, your feedback is appreciated!

EDIT (June 30, 2010): the zip above is outdated and so I removed it. Please use the update site below instead:

http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.6/

Monday, December 14, 2009

Getting GMaven to play nicely with Groovy-Eclipse

There has been a lot of talk on the Groovy mailing list lately about how to get GMaven working with Groovy-Eclipse and coincidentally I saw my friend Mike last night who works at boats.com and he told me that his office has solved this problem. Here is what he told me:



Okay, the first things you will need in your POM file are the GMaven mojo and the GMaven runtime. The dependencies for the mojo seem to be a big buggered out of the box, so you will have to tweak the GMaven runtime dependencies. Here is how you do it:


<dependencies>
<dependency>
<groupId>org.codehaus.groovy.maven</groupId>
<artifactId>gmaven-mojo</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all-minimal</artifactId>
</exclusion>
<exclusion>
<groupId>org.codehaus.groovy.maven.runtime</groupId>
<artifactId>gmaven-runtime-1.5</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.groovy.maven.runtime</groupId>
<artifactId>gmaven-runtime-1.6</artifactId>
<version>1.0</version>
</dependency>
</dependencies>


Next is your build configuration. Eclipse will need to be able to find your Groovy source files, so you will need to add your Groovy source as an explicit resource directory.

The mvn:eclipse plugin will also need to add the Groovy nature to your project.

You will also need to add directives for the GMaven plugin. This is where you will have to do a manual tweak: when using mvn to build your project, gmaven generates some stubs for your groovy code before the java code compiles, so that any java->groovy dependencies will be fulfilled. The Groovy code is then compiled and will overwrite the .class files from the previous step. This breaks things in eclipse, since if you attempt to execute code in your workbench, eclipse sees the stubs and somehow their .class files are what end up in your binary output directory.

Therefore, if you want to execute a gmaven project from Eclipse, you simply need to comment out the stub generation directive and delete any generated stub classes. This is what it looks like:


<build>
<resources>
<resource>
<directory>src/main/groovy</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<configuration>
<additionalProjectnatures>
<projectnature>
org.eclipse.jdt.groovy.core.groovyNature
</projectnature>
</additionalProjectnatures>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.groovy.maven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<goals>
<!--
<goal>generateStubs</goal>
-->
<goal>compile</goal>
<!--
<goal>generateTestStubs</goal>
-->
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

The result is a groovy/java project that can be managed with maven but can be transparently developed (and more importantly, debugged!) in Eclipse.

There is one minor issue that has come up however: GMaven 1.6 stub generation is broken for enums: it generates public constructors, which do not compile. I submitted an issue to codehaus (GMAVEN-51), and apparently the workaround is to use the gmaven 1.7 runtime. I haven't tried it yet, since things are working okay for us thus far.

Mike


So, it does seem like there is a bit of a problem with stubs being recognized by Groovy-Eclipse when they should not be. The solution would be to generate the stubs in a directory that is not seen by Eclipse, rather than in the default output directory. But not knowing much about GMaven (or Maven for that matter) works, I don't know how feasible this is. Can anyone think of a better solution?

Friday, November 27, 2009

New cross-language refactoring support for Groovy-Eclipse plugin

Thanks to the work of Michael Klenk and his students at the Institute for Software in Switzerland, the Groovy-Eclipse plugin now has cross-language refactoring support for renaming. Its use is seamless. Let me give you an example.

Let's start with a simple Java file and a simple Groovy file:

before

You can rename the field in the Java class:

during1

This will perform the standard Java refactoring on all Java classes in your projects. Additionally, the refactoring will be performed on your groovy classes. If there are any ambiguous references to the Java field, the following dialog will open up allowing you to choose which references should be refactored:

choose

And the result is what you would expect:

after

The refactoring can be initiated from the Groovy side as well:

groovyrefactor_menu

As you can see, extract method an inline method are two other refactorings supported in Groovy code.

This functionality is currently available in the latest snapshot build of Groovy-Eclipse for Eclipse 3.5, available from the following update site:
   http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.5/


Please send any questions or problems to the mailing list. We're happy to help. Enjoy!

Monday, November 16, 2009

How type inferencing for Groovy in Eclipse is leading to all sorts of Groovy goodness

Of all of the recent improvements of Groovy-Eclipse since M1 has been released, the groovy type inferencing engine is the most pervasive and powerful. This inferencing engine is able to take a groovy file or code snippet and infer the type of any or all of its expressions. It now forms the core of several of the most prominent features of Groovy-Eclipse. Everything that I describe here is currently available in the latest snapshot release of Groovy-Eclipse for Eclipse 3.5 and the update site is here:
http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.5


In this post, I am going to describe how the inferencing engine is currently used in Groovy-Eclipse and then I will dive a deep and describe how it is implemented. I'll end the post by describing our plans for immediate the future.

What type inferencing is used for



Underlining statically indeterminate references


The Groovy editor now underlines all references that cannot be determined while editing. This happens in a background thread while you type. So, the following code (notice the mispelling of asImmutable):


def aList = []
def otherList = aList.asImmutabl()
otherList.asImmutable()



...will appear in the editor like this...

underline

...and when the typo is fixed, the underlines go away...

nounderline

Code is re-analyzed after every keystroke (well, actually, it is after every keystroke that is followed by 500ms of no keystrokes). The type of every reference in the file is looked up using the inferencing engine. Every reference that cannot be found is considered to be unknown and will be underlined.

Of course, Groovy being a dynamic language, not all references can be statically linked to a type (other than Object). This is especially the case when working with DSLs (such as Grails) that use metaprogramming to add new methods and fields to existing types. Later, I'll describe our plans for extensibility.

Search



Inferencing is used for Java Search as well. Using the search short cut (CTRL-Shift-G or CMD-Shift-G on Mac) or running search from the Java Search dialog, references from and to Groovy code snippets can be located. However, there are some difficulties here. Compared to Java, Groovy is quite a bit more flexible in the ways that fields and methods can be referenced. For example, this field is assigned a closure and referenced as if it were a method:

class Foo {
def x = { print it }
}

x("Hello!")


And this method is referenced in a way that is statically indistinguishable from a field:


class Foo {
def x() { print "Hello!" }
}

x


Furthermore, the spread operator and the use of default parameters makes the Java way of determining which method or field is referenced impossible. So, Groovy search is significantly more liberal than Java search in what it considers a search match. If a reference corresponds to any field, method, or property in the declaring type (regardless of parameter count and whether it is accessed as a field or method), then the match is considered successful. This leads to the following scenarios.

1. Performing search on the field declaration finds field references here:
searchForField

2. Performing search on the method declaration finds exactly the same references:
searchForMethod

And, notice that references attached to the wrong type are not considered a match.
searchNoResults

Content Assist



Most of the internals of Groovy-Eclipse has been rewritten since the previous 1.5.7 release. The most recent rewrite is of content assist. Although the previous version worked (usually), there was always a noticeable lag while waiting for the content assist window to appear. This previous implementation of content assist used a combination of Java classloading and Groovy AST matching in order to find all completion proposals available at a given location, which was both messy and slow. Now, we have hooked into the inferencing engine and have completely rewritten proposal generation with extensibility in mind. And we have made some remarkable improvements in performance.

Using the following code snippet I performed content assist 5 times under the old implementation and the new and took the average of the last 3 of each:


def x
x.hasProperty("foo").





Old implementation2315 milliseconds
New implementation8 milliseconds


That's right...there is a 3 orders of magnitude speed up under the new implementation, making content assist a much more pleasant feature to use. But please note that this is just the time for calculation of the proposals and it does not include the time required to actually pop up the window.

Open declaration


The implementation of open declaration (or code select as it is called in JDT because it is a general mechanism to determine what the current selection refers to and is also used for things like calculating hovers) also uses inferencing. Pressing F3 whenever the selection is any form of "navigateToMe" in the following code, the expected declaration will open:

navigateBefore ---> navigateAfter

Although Groovy-Eclipse M1 used type inferencing for code select, it was using the inferencing from content assist and was subject to the same mistakes, duplicate answers, and slowness that content assist had.

How it works


The inferencing engine has unified the implementation of several of Groovy-Eclipse's most widely used features. With this unification, we have been able to significantly reduce the amount of duplicated code, improve performance, and plan for extensibility.

There are four interacting components of the inferencing engine:


  1. The Abstract Syntax Tree (or AST): this is an abstract representation of the syntax of a groovy file expressed hierarchically. The code to work on can be obtained either from the disk, or it can be the latest in memory. The AST is generated by the Groovy compiler based on the current state of the editor whenever Eclipse is otherwise idle.

  2. The type inferencing visitor: this visitor walks the AST (i.e., uses a visitor pattern to visit every AST node) and delegates to the type lookups and the requestor described below. A new visitor is created for each call to the inferencing engine.

  3. The type lookups: these objects lookup the type information of a particular expression using whatever mechanism they require. They can store and retrieve information about types in a way that all other lookups can use. In this way, each lookup can do what it can and coordinate with other lookups where required. Several default lookups are provided including a simple lookup, an inferencing lookup (that stores information about types via assignment expressions), and a category lookup (that looks up types in Groovy categories). Type lookups are created from a registry on each call to the inferencing engine.

  4. The requestor: this is the object that collects the type information determined by the lookups. The requestor is general and can be used as a way to store the types of all expressions, lookup the type of a particular expression, or look for expressions with unknown types (just to name a few uses). The requestor is created by client code and past to the visitor to start the inferencing process.



The following diagram shows process and how the different pieces work together:

arch


  1. The visitor visits an AST expression node

  2. The visitor sends this expression to the type lookups.

    • each lookup responds with a type and its confidence in that type (ie, exact, potential, inferred...), or null, if it cannot find a type

    • a lookup may additionally store information in a scope object that is passed around to other lookups so that information can be shared between them


  3. The result of the lookup is sent to the requestor

  4. The requestor processes the result and may choose to end the visit or continue.

  5. If required, the visitor will continue to visit the rest of the abstract syntax of the Groovy file



What's next


One of the core design goals of the inferencing engine is extensibility. The inferencing engine must be extensible in two ways. First, it must be usable to help support new features such as quick fixes and refactoring. Also, DSLs, (most notably Grails) must be able to extend inferencing with their own type lookups. It should be apparent through the description of the variety of ways that the engine is currently used that it versatile and can become the core implementation of any number of new Groovy-Eclipse features. As for DSL extensibility, that has not yet been implemented, but the stubs are available. Other DSLs will need to implement their own type lookup, but there is currently no way for these new lookups to be plugged in. Fortunately, it will be possible to use Eclipse's plugin architecture here and this will be the subject of my next blog post (after it gets implemented).

Tuesday, September 29, 2009

Groovy-Eclipse plugin now supports Eclipse 3.5.1

The Groovy-Eclipse plugin can now be installed into Eclipse 3.5.1. As always, the update site is here:

http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.5/

You can stop reading here if you don't care how I got this to work, or why I am a little bit surprised by this.

The Groovy-Eclipse plugin uses a feature patch on the JDT core bundle in order to achieve a high level of integration with JDT. Typically, feature patches can only apply to a specific version of a feature (e.g., 3.5.0.v20090527-2000-7r88FEeFJePyvYeA33DjZ_c1 or some ugliness). This means that Groovy-Eclipse, until recently could only install in a specific version of Eclipse, the one that ships the JDT feature version 3.5.0.v20090527-2000-7r88FEeFJePyvYeA33DjZ_c1. However, Andrew Niefer describes how to get around this by editing the content.xml file to widen the range that the patch applies to. Excellent stuff, and lucky for me, because I didn't want to branch the Groovy-Eclipse code base every time a new service release of Eclipse comes out.

Unfortunately, his instructions were not entirely accurate. Andrew mentions that the range attribute in the <patchscope> element needs to be widened in order allow the patch to be installable on multiple versions. I tried exactly what he suggested, but could not get my patch to install in both 3.5.0 and 3.5.1. After a little bit of exploration, I found that I also needed to change the range attribute in the <lifecycle> element.

All this meant was adding a single line to my ant script, to be executed after the p2 repository is generated:

    
<replace file="${updateSiteDir}/content.xml"
summary="yes" token="${orig.jdt.feature.version.range}"
value="${new.jdt.feature.version.range}"/>

And this turned out to be very simple. Thanks for the hint!

Wednesday, June 3, 2009

Update on Greclipse 2.0 (Groovy support in Eclipse)

Today, I decided to run EclEmma on the Groovy-Eclipse plugin test suite. Shockingly, I found that JUnit support, content assist, and code navigation are completely test-fress. So, I took a stab at creating a solid and flexible test infrastructure that makes it easy to write plugin tests, especially ones that exercise the Java model.

How did I do this? I did it by hooking into existing JDT functionality, just like I have been doing for the rest of my work on Greclipse. I downloaded the JDT core tests from CVS and used what I needed. Now, I can write extremely concise tests that exercise the code I want it to. For example, I wrote the following test case that tests code selection on a closure in a groovy script (i.e., this is the code that is executed when you perform Open Declaration in an editor):


public void testCodeSelectClosure() throws Exception {
IPath projectPath = createGenericProject();
IPath root = projectPath.append("src");
String contents =
"def x = {\n"+
"t -> print t\n"+
"}\n"+
"x('hello')\n";
env.addGroovyClass(root, "Script", contents);
GroovyCompilationUnit unit = getGroovyCompilationUnit(root, "Script.groovy");
IJavaElement[] elts = unit.codeSelect(contents.lastIndexOf('x'), 1);
assertEquals("Should have found a selection", 1, elts.length);
assertEquals("Should have found local variable 'x'", "x", elts[0].getElementName());
}


I have written many tests for JUnit support (or is that GUnit?), and some for code selection. I still need to work on content assist. So, I can now be confident that as we make changes towards the 2.0 release, we are not sliding back in functionality. There is a lot of good work that has already been put into the plugin (including groovy-aware type inference for content assist) and we need to make sure that this does not regress as we go forward.

Sunday, May 17, 2009

Is it Groovy or is it Java? Do we really care?

Well, I care, but the editor doesn't. Following up on the work that Andy Clement has done to modify the JDT compiler so that it can process Groovy as well as Java, I've spent the last few weeks working on editor support for Groovy in Eclipse. Yes, there is already an Eclipse plugin for Groovy, but this particular plugin is tied to a split compiler (JDT for Java files and Groovy compiler for Groovy files). And I wanted to see if we could use the joint compiler to drive Groovy tool support.

So far, with minimal changes it seems that it is possible. I've been able to implement much of the standard Java editing support for Groovy files (e.g., open declaration, search, syntax highlighting, content assist, etc) as long as the editor is passed the right kind of structure.

Let's recap what Andy has done so far...
After getting a little intro to groovy joint compilation (and groovy compiler structure) from Jochen Theodorou, I wondered how hard it would be to go about things slightly differently. Let the Eclipse JDT compiler take control of building a mixed code base but whenever it needed to deal with groovy - call the groovy compiler. If JDT could take control I could see all sorts of things 'just working' in Eclipse, problems upon which we are still expending large amounts of efforts to try and solve for AspectJ. If I could just plug groovy type reference resolution and classfile generation into JDT correctly then references between java and groovy artifacts would work and JDT would ensure incremental compilation worked, even across restarts of eclipse (this latter problem still hasn't been solved for AspectJ).
So originally, this worked great for joint compilation, but as soon as you try to open this in an editor, things just don't behave correctly...



The outline view is out of order, problems and errors don't show up where you expect them to, and source code navigation is broken.

After a bit of mucking around, I found that there is a single root to this problem: source locations. The JDT tooling and the editor is very specific about the source locations that it requires. JDT requires the start and end locations of identifiers, method and type bodies, and all declarations. However, the Groovy compiler (which builds its AST from antlr), only provides line and column information for AST nodes. Although it is possible to translate from line/column to file offset, offsets for identifiers are lost.

And, with a little bit of hacking, I was able to recreate source offsets for field, method, and class names and declarations. It turns out that this is enough to spring to like much of your favorite editor functionality:



Same program, same error as before, but now the outline view is correct, the error marker is in the proper location,* "Open Declaration" and searches navigate to the proper location. And even source hovers work:



So, let's take a deeper look at how this works.

The JDT uses three representations of abstract syntax of a Java file. There is the compiler AST, which is used to generate byte code and perform analysis. There is DOM (document object model) AST, which is used for refactoring and source code manipulation. And, there is the model (the IJavaElement hierarchy), which is more abstract, and is used for navigation and in places like the Package Explorer and Outline views.

There is also support to translate from the compiler AST to either of the other two ASTs. Earlier, I described (or rather Andy described) the way JDT can be used to drive the joint compilation of Groovy source code. The result is a JDT compiler AST with (crucially) the right source code offsets. This, when translated to the right kind of AST at the right time, can be fed to the different parts of JDT that require it. And, thereby, things just work.

A few caveats, of course. Right now, we are only generating a minimal compiler AST (class, method and field declarations only---method bodies are ignored for now). This, however, does seem to be enough for this basic functionality. Also, there are some major differences between Groovy and Java syntax (closures, regular expressions, and lists operations). We are currently sidestepping this problem by ignoring method bodies, statements and expressions.

At some point, however, we are going to have to tackle these loose ends. We will need to be more complete about our ASTs and fill in method bodies (this part is *just* lots of coding). And also, we will need to figure out how to translate Groovy-specific syntax into JDT AST, probably by sub-classing the existing API (this will require some thorough design work *and* lots of coding). So, there is still a lot of work to be done.

What does this mean? Well for one thing it means that the implementation of the Groovy editor is just this:


public class GroovyEditor extends CompilationUnitEditor {
private GroovyImageDecorator decorator = new GroovyImageDecorator();

@Override
public Image getTitleImage() {
return decorator.decorateImage(null, getEditorInput().getAdapter(IFile.class));
}
}

Yes, it is just a minimal subclass of the standard CompilationUnitEditor that merely ensures that the icon in the title bar indicates Groovy, not Java.

So, who cares if it's Groovy or Java? Certainly not the editor.

* There are currently Groovy compiler limitations with error reporting in that error locations only include the character or two of the error.