Friday, August 04, 2006

Groovy Monkey: Eclipse Icons script in Groovy Monkey Pt 1/4

Since I have been working on Groovy Monkey, I have been trying to reuse the icons that are provided by the Eclipse Platform. Why? Well Eclipse does have its look and feel and where concepts in Groovy Monkey are similar to those in Eclipse, why not make it look similar? Besides I am lazy and the thought of creating a bunch of icons does not float my boat.

So I took particular interest in what Mr. Ben Walding posted. He wrote a script that would goto cvs and check out the icon folders from the Eclipse projects themselves and place them on your local system. This is something I have considered myself, though I thought more about using your local install of the Eclipse SDK and extracting them.

Still what he wrote was a Shell script and if it can work for you, use it. It did get me to thinking, why not try and rewrite it as a Groovy Monkey script so that you can run it in Eclipse and dump it into your workspace? This could be a really nice testcase for Groovy Monkey.

So I am going to be updating this post based on my results in attempting to write it in Groovy Monkey. I mean I am writing this as I am trying to write the script and so do not be surprised if there are a few dead ends and the like.

The first step is to setup Eclipse for this. We need to add the repository location :pserver:anonymous@dev.eclipse.org:/home/eclipse to the CVS Repository Explorer. Validate the connection by opening the HEAD branch under the explorer and browse around a bit just to make sure it is working. So now we should have access to the repositories to do a checkout.

The next step is a bit tricky since the Eclipse Team people have yet to publish a public API for CVS, so now we have to fumble around and find what we want. I start by looking at the Eclipse Help under the "Platform Plugin Developer Guide" and check out what is there under "Team Support." On that page I see the following blurb of text:

<extension
point="org.eclipse.team.core.repository">
<repository
class="org.eclipse.team.internal.ccvs.core.CVSTeamProvider"
id="org.eclipse.team.cvs.core.cvsprovider">
</repository>
</extension>


The specifics are not as important as the fact that I have found my first clue, use Ctrl-Shift-T to open the class CVSTeamProvider and check it out. As I scan the API and the documentation on Repository Providers, I realize that they are mapped directly to projects and I am not sure that is what I want to do. Still one thing did catch my eye as a first target of a Groovy Monkey script. The RepositoryProvider class has static methods to allow you to find out what Repository Providers exist currently in your workspace. So I create my new Groovy Monkey Script using the "File > New > Other > Groovy Monkey > New Groovy Monkey Script" menu item to start the wizard. I name it getEclipseIcons.gm and here it is.


--- Came wiffling through the eclipsey wood ---
/*
* Menu: Get Eclipse Icons
* Kudos: ervinja
* License: EPL 1.0
* DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
* Include-Bundle: org.eclipse.team.core
*/
import org.eclipse.team.core.RepositoryProvider
for( providerID in RepositoryProvider.getAllProviderTypeIds() )
{
out.println "providerID: ${providerID}"
}
--- And burbled as it ran! ---


In the Monkey output console I get the following:

providerID: org.eclipse.team.cvs.core.cvsnature
providerID: org.eclipse.pde.core.BinaryRepositoryProvider
providerID: org.polarion.team.svn.core.svnnature


So now I know that I have three providers, one for cvs, another one that the PDE is providing and my subversion provider.

Well the above script is interesting and does highlight in a simple way some of the power of Groovy Script, it does not get me closer to what I want. So I go open up the CVSRepositoryProvider in the Editor and use the "Show In > Package Explorer" pop-up menu command to show me where the code is located. I see it is in the org.eclipse.team.cvs.core plugin and I notice the CVSProviderPlugin class.

The CVSProviderPlugin class seems more interesting and I begin to explore by rewriting the script to the following:

--- Came wiffling through the eclipsey wood ---
/*
* Menu: Get Eclipse Icons
* Kudos: ervinja
* License: EPL 1.0
* DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
* Include-Bundle: org.eclipse.team.cvs.core
*/
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin

def plugin = CVSProviderPlugin.getPlugin()
for( location in plugin.getKnownRepositories() )
{
out.println "Repository Location: ${location.dump()}"
}
--- And burbled as it ran! ---

The output in the console is:

Repository Location: <org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation@63adab5c method=org.eclipse.team.internal.ccvs.core.connection.PServerConnectionMethod@262e12 user=anonymous password=null host=dev.eclipse.org port=0 root=/home/eclipse userFixed=true passwordFixed=false allowCaching=false serverPlatform=1 previousAuthenticationFailed=false>
Repository Location: <org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation@553087e method=org.eclipse.team.internal.ccvs.core.connection.PServerConnectionMethod@262e12 user=anonymous password=null host=dev.eclipse.org port=0 root=/cvsroot/technology userFixed=true passwordFixed=false allowCaching=false serverPlatform=0 previousAuthenticationFailed=false>
Repository Location: <org.eclipse.team.internal.ccvs.core.connection.CVSRepositoryLocation@6142ea28 method=org.eclipse.team.internal.ccvs.core.connection.ExtConnectionMethod@7b51d2 user=xxxxx password=null host=groovy-monkey.cvs.sourceforge.net port=0 root=/cvsroot/groovy-monkey userFixed=true passwordFixed=false allowCaching=false serverPlatform=0 previousAuthenticationFailed=false>


Now this is looking like it can be useful.

On second thought, maybe not, as I scan the Eclipse Platform code a bit more, it really looks like what I want is in the org.eclipse.team.cvs.ui plugin and that I want to try out is the CheckoutIntoOperation. CheckoutIntoOperation requires that I
get some IRemoteFolder(s) and well the above script is useful after all. So I rewrite the script as follows:

--- Came wiffling through the eclipsey wood ---
/*
* Menu: Get Eclipse Icons
* Kudos: ervinja
* License: EPL 1.0
* DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
* Include-Bundle: org.eclipse.team.cvs.core
* Include-Bundle: org.eclipse.team.cvs.ui
*/
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin
import org.eclipse.team.internal.ccvs.ui.operations.CheckoutIntoOperation

def plugin = CVSProviderPlugin.getPlugin()
def repositoryLoc
for( location in plugin.getKnownRepositories() )
{
out.println "Repository Location: ${location.getRootDirectory()}"
if( location.getRootDirectory() == '/home/eclipse' )
repositoryLoc = location
}
for( member in repositoryLoc.members( null, false, null ) )
{
out.println "Repository Path: ${member.getRepositoryRelativePath()}"
}

--- And burbled as it ran! ---

I get the following output from running this modified script:

Repository Location: /home/eclipse
Repository Location: /cvsroot/technology
Repository Location: /cvsroot/groovy-monkey
Repository Path: CVSROOT
Repository Path: cdt-contrib
Repository Path: eclipse-project-home
Repository Path: eclipse-project-website
Repository Path: equinox-incubator
Repository Path: jdt-core-home
Repository Path: jdt-debug-home
Repository Path: jdt-doc-home
Repository Path: jdt-ui-home
Repository Path: org.apache.ant
Repository Path: org.apache.lucene
Repository Path: org.apache.xerces
Repository Path: org.eclipse.ant.core
Repository Path: org.eclipse.ant.optional.junit
Repository Path: org.eclipse.ant.tests.core
Repository Path: org.eclipse.ant.tests.ui
Repository Path: org.eclipse.ant.ui
Repository Path: org.eclipse.compare
Repository Path: org.eclipse.compare.examples
Repository Path: org.eclipse.compare.examples.xml
Repository Path: org.eclipse.compare.tests
Repository Path: org.eclipse.core.applicationrunner
Repository Path: org.eclipse.core.boot
Repository Path: org.eclipse.core.commands
Repository Path: org.eclipse.core.components
Repository Path: org.eclipse.core.contenttype
Repository Path: org.eclipse.core.expressions
...


Now this is starting to look like the script described in Mr Walding's blog posting. We have a hint that we want to use the CheckoutIntoOperation and we now have access to the remote folders.

Since each of these CVS operations can take some time to complete, I need to have a way to allow the user to monitor progress and cancel the script if they desire. Therefore next step I realize I should do is start using the monitor variable. I also want to begin to check the individual eclipse projects for icons. I know, I think, that Eclipse CVS projects like to put all their icon files into a top level directory called icons, so I am going to check for them.


--- Came wiffling through the eclipsey wood ---
/*
* Menu: Get Eclipse Icons
* Kudos: ervinja
* License: EPL 1.0
* DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
* Include-Bundle: org.eclipse.team.cvs.core
* Include-Bundle: org.eclipse.team.cvs.ui
*/
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin
import org.eclipse.team.internal.ccvs.ui.operations.CheckoutIntoOperation

def plugin = CVSProviderPlugin.getPlugin()
def repositoryLoc
for( location in plugin.getKnownRepositories() )
{
out.println "Repository Location: ${location.getRootDirectory()}"
if( location.getRootDirectory() == '/home/eclipse' )
repositoryLoc = location
}
monitor.beginTask( '', members.size() )
for( member in repositoryLoc.members( null, false, null ) )
{
if( monitor.isCanceled() )
return
if( !member.childExists( 'icons' ) )
{
monitor.worked( 1 )
continue
}
out.println "Repository Path: ${member.getRepositoryRelativePath()}"
}
monitor.done()
--- And burbled as it ran! ---


I get a surprising result from the script above, it doesn't work. I fumble about the API and notice an interesting method called fetchChildren(). I am guessing that information is cached in these classes only on request, so I add the call.


--- Came wiffling through the eclipsey wood ---
/*
* Menu: Get Eclipse Icons
* Kudos: ervinja
* License: EPL 1.0
* DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
* Include-Bundle: org.eclipse.team.cvs.core
* Include-Bundle: org.eclipse.team.cvs.ui
*/
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin
import org.eclipse.team.internal.ccvs.ui.operations.CheckoutIntoOperation

def plugin = CVSProviderPlugin.getPlugin()
def repositoryLoc
for( location in plugin.getKnownRepositories() )
{
out.println "Repository Location: ${location.getRootDirectory()}"
if( location.getRootDirectory() == '/home/eclipse' )
repositoryLoc = location
}
monitor.beginTask( '', members.size() )
for( member in repositoryLoc.members( null, false, null ) )
{
member.fetchChildren()
if( monitor.isCanceled() )
return
if( !member.childExists( 'icons' ) )
{
monitor.worked( 1 )
continue
}
out.println "Repository Path: ${member.getRepositoryRelativePath()}"
}
monitor.done()
--- And burbled as it ran! ---


Well the script now does what I expected to. So to complete this exercise I collect those repository resources that correspond to icon folders and place them into a list called iconFolders and prepare to make the call to CheckoutIntoOperation. I did have one more intermediate step here, I tried to check out all the icons into the same set of folders, but the CheckoutIntoOperation would wipe the directory for each project. So we check them out into a project that already exists in my workspace called GroovyMonkeyExamples and into a folder called icons. We place the icon folders into subdirectories under icons that correspond to their remote repository path. To complete we instanciate the CheckoutIntoOperation and invoke its execute() method.


--- Came wiffling through the eclipsey wood ---
/*
* Menu: Get Eclipse Icons
* Kudos: ervinja
* License: EPL 1.0
* DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
* Include-Bundle: org.eclipse.team.cvs.core
* Include-Bundle: org.eclipse.team.cvs.ui
*/
import org.eclipse.core.runtime.SubProgressMonitor
import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin
import org.eclipse.team.internal.ccvs.core.ICVSRemoteFolder
import org.eclipse.team.internal.ccvs.ui.operations.CheckoutIntoOperation

def plugin = CVSProviderPlugin.getPlugin()
def repositoryLoc
for( location in plugin.getKnownRepositories() )
{
if( monitor.isCanceled() )
return
if( location.getRootDirectory() == '/home/eclipse' )
repositoryLoc = location
}

def targetProject = workspace.getRoot().getProject( 'GroovyMonkeyExamples' )
def members = repositoryLoc.members( null, false, null )
monitor.beginTask( '', 2 * members.size() )
def iconFolders = []
for( member in members )
{
member.fetchChildren()
if( monitor.isCanceled() )
return
if( !member.childExists( 'icons' ) )
{
monitor.worked( 1 )
continue
}
def icons = member.getFolder( 'icons' )
iconFolders.add( icons )
monitor.worked( 1 )
}
iconFolders.each
{ folder ->
if( monitor.isCanceled() )
return
def targetFolder = targetProject.getFolder( 'icons' ).getFolder( folder.getRemoteParent().getRepositoryRelativePath() )
new CheckoutIntoOperation( null, folder, targetFolder, true ).execute( new SubProgressMonitor( monitor, 1 ) )
monitor.worked( 1 )
}
monitor.done()

--- And burbled as it ran! ---


This now gets what we want. We check all the Eclipse projects for icons and get them all. It can be left for homework to try and automate the task to disconnect the project from CVS and to package it up into a zip archive.

There are a few notes to make here. First, this is a quick prototype and there may very well be far more efficient ways to accomplish this. In fact, you could write a seperate script to grab all the icon folders and print them out so that you can paste them into this script and check only those remote projects that have icons. A second note to make is, well this prototype only took a couple of hours to accomplish and that was with my less than ideal setup ( and I do mean less than ideal ). Imagine the time it would have taken using self hosting or deploying plugins. The last note is that of course it would be been easier to install cvs on my system and script commands to call it directly, but that would have been cheating since the whole point was to demonstrate how you could use Groovy Monkey to navigate the Eclipse Platform API world directly and easily.

So we do have a functional script, but it is not very asthetically pleasing ( at least to me ) and the ability to check out remote resources from CVS into my workspace is probably something I want to do again. So in future blog entries I want to demonstrate how we can split off some functions into seperate libraries scripts that can be reused or how you can begin to prototype some plugins by starting to write the function as DOM in your workspace and be able to runtime deploy it into your current Eclipse instance.

External Link

No comments: