posts - 1,  comments - 25,  trackbacks - 0
Summary:  Eclipse classpath containers are an organized, user-friendly way to manage Java™ libraries in Eclipse. Since a group of libraries can be referenced as one abstract name, they can be removed and added as a single entry easily. The view of the libraries is also simplified in the Java Perspective as a single entry that can be expanded to view the entire set. Since the set of libraries is defined by an implementation of IClasspathContainer, it can be redefined dynamically. This tutorial shows how to implement a custom IClasspathContainer with accompanying extensions of classpathContainerInitializer and classpathContainerPage.

Before you start

About this tutorial

This tutorial focuses on Eclipse classpath container functionality. It starts by explaining some Eclipse classpath concepts, then guides you through the details of implementing a classpath container. You will create a simple Eclipse plug-in that provides an implementation of IClasspathContainer, and extends the classpathContainerIntializer andclasspathContainerPage extension points to make the container accessible to the user. You will also implement a Java element filter to filter files included in the classpath container from the Java Package Explorer.

Objectives

In this tutorial, you will learn:

  • The basic concepts behind the Eclipse JDT classpath
  • How to implement IClasspathContainer
  • How to extend classpathContainerInitializer
  • How to extend classpathContainerPage
  • A method for filtering contained classpath entries from the Java Package Explorer, so they are not unintentionally added as duplicates

Prerequisites

This tutorial is written for Eclipse programmers whose skills and experience are at an intermediate level. It is expected that you understand the Eclipse Platform architecture, the basics of extending the platform, as well as Eclipse Java projects and how the classpath is used in those types of projects. The code uses some features that were new to Java 5, such as Generics. The usage is small, but it will help to have an understanding of Java 5 features.

System requirements

To run the examples, you need:

Eclipse V3.2 or later
Although you may have some success with earlier versions, the code in this tutorial was tested with Eclipse V3.2.2, which was the latest official release at the time of this writing.
JDK V1.5 or later, from IBM or Sun Microsystems
Some Java features new to version 5 are used in this tutorial to a very small degree, such as Annotations and Generics

Introduction to classpath containers

Classpath containers are an effective way to organize project resources by grouping them under one logical classpath entry. Whether you realize it or not, you may have used a classpath container. The most recognized classpath container among Java developers is the JRE System Library. Every Java project has a JRE System Library in the classpath. Other notable classpath containers are the JUnit and Plug-in Dependencies containers included in the base Eclipse project, as well as the Web App Libraries container, which is part of the dynamic Web project type in the Web Tools Project (WTP). Before we start implementing our own classpath container, let's start by reviewing the different types of classpath entries.

Kinds of classpath entries

Every Java project includes a .classpath file that defines the classpath of the project. This file is generally not edited by hand but created and modified by the JDT plug-in as the user changes the Java build path properties of a project. Each entry in the classpath has a kind attribute and a path attribute, along with various other optional attributes depending on the kind. The order the entries appear in the file from top to bottom determines their order in the project's classpath. Before continuing, it is a good exercise to create a Java project and experiment with that project by changing the Java build path properties, creating entries and modifying the initial default entries to arrive at the .classpath file shown in Listing 1.


Listing 1. Sample .classpath file showing all entry kinds
            <?xml version="1.0" encoding="UTF-8"?>
            <classpath>
            <classpathentry kind="src" path="src"/>
            <classpathentry kind="lib" path="lib/derby.jar" sourcepath="lib/derby-src.jar"/>
            <classpathentry kind="var" path="ECLIPSE_HOME/startup.jar"/>
            <classpathentry combineaccessrules="false" kind="src" path="/bar"/>
            <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
            <classpathentry kind="output" path="bin"/>
            </classpath>
            

Listing 1 shows six classpath entries marked by five kinds. In the underlying Eclipse Java model, each of these is represented by an org.eclipse.jdt.core.IClasspathEntry type, which categorizes the entry-by-entry kind and content kind. A contentkind is defined for most entry kinds and is either source (org.eclipse.jdt.core.IPackageFragmentRoot.K_SOURCE) or binary (IPackageFragmentRoot.K_BINARY). In Eclipse V3.2, the IClasspathEntry interface defines five constants to refer to the classpath entry kind, but these are not the same five marked in Listing 1. In fact, one shown in this file — namely output — is not defined as a constant in IClasspathEntry, and another — src — is shown twice in Listing 1 because the two are represented as separate constants in IClasspathEntry. This makes it a little confusing, but let's ignore that for now and start from the top of the file.

Project source folders

The first entry in Listing 1 has kind="src" with path="src". This indicates that the project has a source folder located in a directory relative to the project root called src. This will have several implications for the project — most notably, it tells the Java builder that it needs to compile the source located in the src directory. This entry kind is represented by the constantIClasspathEntry.CPE_SOURCE and has a content kind of IPackageFragmentRoot.K_BINARY. When Java projects are created, they inherit default source and output folders from workspace preferences (in Eclipse see Window > Preferences > Java > Build Path). In a fresh Eclipse installation, the source and output folders will be set to the root project folder. The first srcentry in Listing 1 has a path="src" because the workspace default source folder was changed to src.

Binary libraries

The second entry in Listing 1 has kind="lib" with path="lib/derby.jar". This indicates that there is a binary classpath entry located at lib/derby.jar relative to the project root. lib entries refer to a set of class files with the path attribute specifying a directory of .class files or an archive file that includes .class files. This entry also includes the optional sourcepath attribute, which refers to the location of the source attachment. The lib kind entry is represented by the entry kind constantIClasspathEntry.CPE_LIBRARY and has a content kind of IPackageFragmentRoot.K_BINARY.

Classpath variable extensions

The third entry in Listing 1 has kind="var" with path="ECLIPSE_HOME/startup.jar". In this entry, ECLIPSE_HOME is a variable. This built-in variable provided by Eclipse refers to the Eclipse installation directory. Custom variables can also be defined by the user for a workspace. Paths to classpath files are always relative to the project directory or absolute. So it's good practice to use variables for referencing files outside the project directory to avoid absolute paths, which will make the project difficult to share across different environments. See Window > Preferences > Java > Build Path > Classpath Variables for a list of the variables defined in your workspace. After a variable is defined, it can be used in the build path settings by extending it to refer to a file that is located relative to the variable.

In this case, it was extended by adding the Eclipse startup.jar. var entries are similar to lib entries, except that the first element in the path is evaluated before using it in the classpath. Another difference between var and lib entries is that lib entries can reference an archive or a folder of classes, whereas var entries can only reference an archive. The var kind is a binary type of entry represented by the constant IClasspathEntry.CPE_VARIABLE and the content kind is undefined. So, you should never use getContentKind() when referring to an IClasspathEntry with kind="var". It is interesting to note, however, that one can only add binary variable extensions to the classpath, and to make it confusing, getContentKind() always returnsIPackageFragmentRoot.K_SOURCE for variable entries.

Prerequisite projects

The fourth entry in Listing 1 has kind="src" with path="/bar". This entry references the source of another project in the workspace (notice that the path starts with /, which refers to the workspace root directory). This will, in effect, add the configured output folder for the /bar project to the compilation classpath, as well as any classpath entries exported from the project. While the kind attribute is equal to "src", as it is with IClasspathEntry.CPE_SOURCE above, this entry is represented by a different entry kind constant, IClasspathEntry.CPE_PROJECT, and it has a content kind of IPackageFragmentRoot.K_SOURCE. The only difference in the .classpath entry is that the path attribute is relative to the workspace, instead of the project.

Classpath containers

The fifth entry in Listing 1 has kind="con" with path="org.eclipse.jdt.launching.JRE_CONTAINER". This type of entry is the subject of this tutorial. The kind constant for this entry is IClasspathEntry.CPE_CONTAINER, and the content kind is undefined. A container entry is a logical reference to an implementation of org.eclipse.jdt.core.IClasspathContainer. The implementation aggregates a set of concrete entries, either of type IClasspathEntry.CPE_LIBRARY orIClasspathEntry.CPE_PROJECT.

Summary of classpath entries

A summary of the classpath entry types is provided below. The example entries are taken from the .classpath file shown inListing 1.


Table 1. Summary classification of classpath entries in Listing 1
Example entryIconEntry kindContent kind
<classpathentry kind="src" path="src"/> Project Source Folder CPE_SOURCE K_SOURCE
<classpathentry kind="lib" path="lib/derby.jar" sourcepath="lib/derby-src.jar"/> Library CPE_LIBRARY K_BINARY
<classpathentry kind="var" path="ECLIPSE_HOME/startup.jar"/> Variable CPE_VARIABLE undefined
<classpathentry combineaccessrules="false" kind="src" path="/bar"/> Project Prerequisite CPE_PROJECT K_SOURCE
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> Container CPE_CONTAINER undefined

More information

For a detailed explanation of the different classpath entries, read the Javadoc for org.eclipse.jdt.core.IClasspathEntry, and for a good lesson on setting the build path programmatically with these different kinds, read the Eclipse help topic "Setting the Java Build Path."

What are classpath containers?

In this tutorial, the term classpath container refers to an implementation of org.eclipse.jdt.core.IClasspathContainer. Classpath containers are logical references to a set of classpath resources. Their entry sets can be defined per project by theIClasspathContainer implementation. Since the entry set is defined by the IClasspathContainer, it can be dynamic or static. As an example of the static case, a container could return a fixed set of JARs that are located in a well-known directory (e.g., the JRE System Library). Or, in a more dynamic case, a container might return a set of runtime JARs that changes depending on server deployment.

Why use classpath containers?

There are many reasons why one might want to use a classpath container. Here are the more important ones:

Reducing clutter
Instead of having several individual JARs under one project, you can group them into a container that can be expanded to see the individual JARs. This conserves screen real estate in the Java Package Explorer view.

And the number of entries needed in the .classpath file is reduced from several to one.
Portability
Since the paths for container entries are logical names, there is no information about the file system included. This makes the classpath entry portable to different machines and, thus, makes it easy to share projects through source control management (SCM) systems, such as CVS or SVN. Variable entries also have this benefit.
Easier management
If a classpath container page is defined for the container type, the user can edit the properties of the container through the UI.
Dynamicity
This is probably the most important benefit, since it cannot be achieved through any of the other classpath entry types. Since the container entries are retrieved at the time they are needed, they can be very dynamic. No information about the container entries persists in the workspace or project metadata. The entries are retrieved when they are needed for operations like compiling and running. This provides a means for redefining the project's classpath dependencies based on practically any factor. In a simple case, a container could add all the JARs in a project directory to the classpath. In fact, this is the function of the Web App Libraries classpath container provided by the Web Tools Project (WTP) for a Web project's WEB-INF/lib folder.

mplementing a classpath container

Create the IClasspathContainer implementation class

First, we need to create a class that implements org.eclipse.jdt.core.IClasspathContainer. This implementation will be the core function of our example plug-in. As with any classpath container, its main purpose is to define a set of classpath entries by returning an array of org.eclipse.jdt.core.IClasspathEntry when asked. Before proceeding, you should read the Javadoc for org.eclipse.jdt.core.IClasspathContainer, particularly the class overview andgetClasspathEntries() method description.

The container in the CpContainerExample project returns a set of IClasspathEntry.CPE_LIBRARY entries. The entries will be files read from a configured project directory. The project directory and the extensions of the files to include as entries will be configured through a classpath container wizard, which will be implemented later, in Implementing a classpath container entry page.

The IClasspathContainer implementation class is called cpcontainer.example.SimpleDirContainer. There are three methods that do the real work of this class: the constructor, getClasspathEntries(), and the accept() method of the inner FilenameFilter class. Let's start by looking at the constructor shown in Listing 2.

The constructor


Listing 2. The SimpleDirContainer constructor
            /**
            * This constructor uses the provided IPath and IJavaProject arguments to assign the
            * instance variables that are used for determining the classpath entries included
            * in this container.  The provided IPath comes from the classpath entry element in
            * project's .classpath file.  It is a three segment path with the following
            * segments:
            *   [0] - Unique container ID
            *   [1] - project relative directory that this container will collect files from
            *   [2] - comma separated list of extensions to include in this container
            *         (extensions do not include the preceding ".")
            * @param path unique path for this container instance, including directory
            *             and extensions a segments
            * @param project the Java project that is referencing this container
            */
            public SimpleDirContainer(IPath path, IJavaProject project) {
            _path = path;
            // extract the extension types for this container from the path
            String extString = path.lastSegment();
            _exts = new HashSet<String>();
            String[] extArray = extString.split(",");
            for(String ext: extArray) {
            _exts.add(ext.toLowerCase());
            }
            // extract the directory string from the PATH and create the directory relative
            // to the project
            path = path.removeLastSegments(1).removeFirstSegments(1);
            File rootProj = project.getProject().getLocation().makeAbsolute().toFile();
            if(path.segmentCount()==1 amp;amp; path.segment(0).equals(ROOT_DIR)) {
            _dir = rootProj;
            path = path.removeFirstSegments(1);
            } else {
            _dir = new File(rootProj, path.toString());
            }
            // Create UI String for this container that reflects the directory being used
            _desc = "/" + path + " Libraries";
            }
            

The constructor will be called by the classpath container initializer, once for each Java project that references the container in the classpath. The initializer will pass two arguments: the container path and the Java project. The container path is an IPath type, which means that it is a multisegment string with each segment separated by a / character. IPaths are typically used to represent paths to files, but that is not the case here.

As with all container paths, the first segment of the path will contain the unique name for this type of container, which iscpcontainer.example.SIMPLE_DIR_CONTAINER. The following segments of the path can be used to provide additional hints about the container.

In our case, we will use these segments to provide two additional pieces of information used for constructing the container. The second segment of the path will contain the project relative directory that will contain the files to be included in this container. This directory must be the project directory itself or a subdirectory of the project. We will use - as a special character to indicate that the directory is the root project directory, since we cannot have a / by itself or an empty string in the IPath segment. The third segment of the path will contain a comma-separated list of file extensions to include in our container. The extensions will not include the preceding . character, since the . character has special meaning in IPath segments. An example of a classpath entry using this path scheme is shown below. In this example, the container will collect entries from the project's local /lib directory, and it will accept files with a .jar or .zip extension.

 <classpathentry kind="con" path="cpcontainer.example.SIMPLE_DIR_CONTAINER/lib/jar,zip"/>
            

Given the container path and the Java project, the constructor builds the instance variables used later in thegetClasspathEntries() method. First, it records the full container path, which is needed for theIClasspathContainer.getPath() method. Then it starts breaking down the path to extract the configuration for the container. The last segment is tokenized into an array of extensions, and the middle segment is appended to the project directory to form the absolute directory path. The directory is not checked for validity here. Instead, an isValid() method is provided for the container, which confirms that the directory exists and is indeed a directory. The initializer will call this method after construction to validate the container before setting it in the Java model. Finally, the constructor builds a string description of the container for the UI that displays the configured directory.

Before studying the core method of this class, getClasspathEntries(), we need to look at the FilenameFilter, shown in Listing 3, since it will be used by getClasspathEntries() to filter the file list.

The filename filter


Listing 3. The FilenameFilter implementation used by cpcontainer.example.SimpleDirContainer
            /**
            * This filename filter will be used to determine which files
            * will be included in the container
            */
            private FilenameFilter _dirFilter = new FilenameFilter() {
            /**
            * This File filter is used to filter files that are not in the configured
            * extension set.  Also, filters out files that have the correct extension
            * but end in -src, since filenames with this pattern will be attached as
            * source.
            *
            * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
            */
            public boolean accept(File dir, String name) {
            // lets avoid including filenames that end with -src since
            // we will use this as the convention for attaching source
            String[] nameSegs = name.split("[.]");
            if(nameSegs.length != 2) {
            return false;
            }
            if(nameSegs[0].endsWith("-src")) {
            return false;
            }
            if(_exts.contains(nameSegs[1].toLowerCase())) {
            return true;
            }
            return false;
            }
            };
            

This FilenameFilter will be passed as an argument to java.io.File.listFiles() to determine which files get returned as classpath entries. If the accept() method returns true, the file will be included.

The first check is for files that match the *-src.* pattern. We want to exclude files like this in our set of classpath entries because we will use a *-src.* naming convention to indicate that the archive contains source files that should be attached to a binary archive with the same name (excluding the -src). So, for example, we may have an entry called mylib.jar in our container directory. Using the -src convention, we can also include an archive file called mylib-src.jar in the directory that contains the corresponding source files for the classes in mylib.jar. As we will see later, the getClasspathEntries() method will honor this convention by creating a single CPE_LIBRARY entry for mylib.jar with the attached source archive mylib-src.jar. In fact, Eclipse JDT uses this convention to search for the source for basic CPE_LIBRARY entries when the source has not already been attached by the user. Eclipse does not provide this behavior for free, however, when the CPE_LIBRARY entries are included as part of a classpath container, so we have to code it ourselves.

Finally, the accept() method checks to see if the file matches any of the configured extensions. If so, it returns true, which indicates that this file should be included as an entry in our classpath container. The set of file extensions and the directory that contains the files is passed to the constructor by the classpath container initializer, which is discussed in Implementing a classpath container initializer. The extensions and directory are configured by the user through the classpath container page, described in Implementing a classpath container entry page. Next, we'll look at the getClasspathEntries() method shown in Listing 4, which is the defining method of the container.

The getClasspathEntries() method


Listing 4. The getClasspathEntries() method of cpcontainer.example.SimpleDirContainer
            /**
            * Returns a set of CPE_LIBRARY entries from the configured project directory
            * that conform to the configured set of file extensions and attaches a source
            * archive to the libraries entries if a file with same name ending with
            * -src is found in the directory.
            *
            * @see org.eclipse.jdt.core.IClasspathContainer#getClasspathEntries()
            */
            public IClasspathEntry[] getClasspathEntries() {
            ArrayList<IClasspathEntry> entryList = new ArrayList<IClasspathEntry>();
            // fetch the names of all files that match our filter
            File[] libs = _dir.listFiles(_dirFilter);
            for( File lib: libs ) {
            // strip off the file extension
            String ext = lib.getName().split("[.]")[1];
            // now see if this archive has an associated src jar
            File srcArc = new File(lib.getAbsolutePath().replace("."+ext, "-src."+ext));
            Path srcPath = null;
            // if the source archive exists then get the path to attach it
            if( srcArc.exists()) {
            srcPath = new Path(srcArc.getAbsolutePath());
            }
            // create a new CPE_LIBRARY type of cp entry with an attached source
            // archive if it exists
            entryList.add( JavaCore.newLibraryEntry(
            new Path(lib.getAbsolutePath()) , srcPath, new Path("/")));
            }
            // convert the list to an array and return it
            IClasspathEntry[] entryArray = new IClasspathEntry[entryList.size()];
            return (IClasspathEntry[])entryList.toArray(entryArray);
            }
            

The getClasspathEntries() method is called when the Java model needs to resolve the classpath for a Java project. Of course, since this is an instance method, the instance will need to be constructed first. Constructing the container is the job of the classpathContainerInitializer, which will be discussed in the next section. So, for now, let's assume the instance has already been constructed.

First, we get a list of files that match the filter. Then, for each one of the matched files, we look for a corresponding source archive with the same extension using the -src convention. Next, we create a new CPE_LIBRARY classpath entry, attaching the source archive if it exists. Finally, we add the entry to a list, which is converted to an array before returning.

That's it! All of the other code included in the sample is to enable this simple function. It is important to realize that this function is called every time the classpath for a project needs to be resolved (before compiling or running, for example). So, you should not waste time in this method as it could noticeably affect the response time of many Java operations. Try to do as much prep work as possible for this method in other less-critical methods, such as the constructor.

Implementing a classpath container initializer

The classpathContainerInitializer extension point

Now that we have a classpath container implementation, we need a mechanism for initializing it. We could initialize it ourselves at startup or anytime before we anticipate its first usage, but this could result in unnecessary initialization, increased start up time, and there might be a reference to the container before we get the opportunity to initialize it. Theorg.eclipse.jdt.core.classpathContainerInitializer extension point provides a mechanism for initializing the container automatically when it is needed. More specifically, when a Java project references the container by ID for the first time, the initializer will bind that reference to a container instance. It is recommended in the Eclipse documentation to provide aclasspathContainerInitializer for each classpath container.

We begin implementing our initializer by first adding the extension to our plugin.xml. Since we have not extended the platform yet, we do not have a plugin.xml in our project. To open the PDE editor for adding the extension, double-click the META-INF/MANIFEST.MF file in the PDE project. Next, click the Extensions tab at the bottom of the editor. Then click Add..., selectorg.eclipse.jdt.core.classpathContainerInitializer, and click Finish.

At this point, you can add a name and ID for the new extension, but it is not necessary. There will also be a new link for reading the extension point documentation called Open extension point description, but if you click through, you will see that it is not a very detailed description. Adding the extension will create a plugin.xml in the root project directory and a plugin.xml tab in the PDE editor. Click on the plugin.xml tab to edit the extension. We need to add a classpathContainerInitializer child element to our extension point, which will provide the ID of the classpath container and name of the Java class that implements the initializer. The classpathContainerInitializer extension element for the sample is shown below.

The id attribute, cpcontainer.example.SIMPLE_DIR_CONTAINER, is the ID used to reference the classpath container, not the initializer. This is the ID that will be used in the .classpath files of the projects that are referencing the container. The ID can be any string as long as it is unique relative to other classpath containers in the Eclipse installation. It is a good practice in general to include package names as prefixes in Eclipse extension IDs to avoid collisions. Finally, the class attribute refers to the class that implements the initializer, which we have not yet created. You will see a warning next to the class attribute until we create the class.


Listing 5. The classpathContainerInitializer extension
            <extension
            point="org.eclipse.jdt.core.classpathContainerInitializer">
            <classpathContainerInitializer
            id="cpcontainer.example.SIMPLE_DIR_CONTAINER"
            class="cpcontainer.example.SimpleDirContainerInitializer"/>
            </extension>
            

Extending the ClasspathContainerInitializer class

To create the class, right-click on the cpcontainer.example package and select New > Class. Type the name of the class, as specified in the extension, cpcontainer.example.SimpleDirContainerInitializer and type the name of the super class,org.eclipse.jdt.core.ClasspathContainerInitializer. At this point, it will be helpful to read the Javadoc fororg.eclipse.jdt.core.ClasspathContainerInitializer. The new class will have a stub initialize() method, which is the only abstract method that is required to be implemented. The initialize() method binds the ID reference to a container instance. The initialize() method from SimpleDirContainerInitializer shown in Listing 6, simply constructs a container and validates it using the container's isValid() method. If the container is valid, it is set in the Java model. Otherwise, the method fails silently by not doing anything. In either case, the initialize() method will not be called again for the given project and container ID combination, since the result will be stored in the Java model for the life of the Eclipse process.


Listing 6. The initialize() method in SimpleDirContainerInitializer
            @Override
            public void initialize(IPath containerPath, IJavaProject project)
            throws CoreException {
            SimpleDirContainer container = new SimpleDirContainer( containerPath, project );
            if(container.isValid()) {
            JavaCore.setClasspathContainer(containerPath, new IJavaProject[] {project},
            new IClasspathContainer[] {container}, null);
            } else {
            Logger.log(Logger.WARNING, Messages.InvalidContainer + containerPath);
            }
            }

There are two other overridden methods from the ClasspathContainerInitializer base class,canUpdateClasspathContainer() and requestClasspathContainerUpdate(). The former determines whether or not the container can be edited in the build path properties dialog with a classpathContainerPage implementation (discussed in the next section). The later updates the container in response to it being edited by a classpathContainerPage implementation. Since we are providing a page to edit the classpath container entry, we override the default implementation ofcanUpdateClasspathContainer() in the base class, to return true. We also provide arequestClasspathContainerUpdate() implementation that simply calls JavaCore.setClasspathContainer() to update the Java model with the updated container instance.

Now that you understand the classpath container implementation and the initializer, you may be wondering how a user can add the container to the classpath. Well, you can always manually add a classpath entry to the .classpath file of your project, but this is not a recommended practice. Fortunately, there is an extension point called classpathContainerPage that allows you to provide a wizard that can be called from the Build Path properties dialog. The classpath container page extension is the topic of the next section.

Implementing a classpath container entry page

How is the page used?

Now that you understand classpath containers and how they are initialized, you are ready to create a classpath container entry page. This page is a wizard that allows the user to add a classpath container or edit an existing one. The user can choose to add a container by selecting Add Library... from the Libraries tab of the build properties dialog, as shown in figures 3 and 4, or by selecting Add Libraries... from the pop-up menu in the Java Package Explorer.


Figure 3. Adding a library in the build properties dialog
Adding a library in the build properties dialog 


Figure 4. Selecting a library to add
Selecting a library to add 

The org.eclipse.jdt.ui.classpathContainerPage extension

The extension point for a classpath container page is org.eclipse.jdt.ui.classpathContainerPage. Let's start by adding an extension using the PDE editor. Double-click on the plugin.xml to open the editor and click the Extensions tab. Then clickAdd... and select org.eclipse.jdt.ui.classpathContainerPage. This will add the extension stub in our plugin.xml file we need to edit. Click on the plugin.xml tab to edit the source of the plugin.xml. We need to add a classpathContainerPage element under the extension point, which requires a name, ID, and class attribute. The classpathContainerPage extension for the sample is shown in Listing 7. Note that the name string has been externalized using the Eclipse plug-in internationalization mechanism, which stores the strings as properties in the file called plugin.properties.


Listing 7. The classpathContainerPage extension
            <extension
            point="org.eclipse.jdt.ui.classpathContainerPage">
            <classpathContainerPage
            id="cpcontainer.example.SIMPLE_DIR_CONTAINER"
            name="%ContainerName"
            class="cpcontainer.example.SimpleDirContainerPage"/>
            </extension>
            

As you can see in Figure 5, the wizard has inputs for the container directory and extension list. These are the two parameters needed for setting the container path for the classpath container instance.


Figure 5. Classpath container wizard page for the Simple Directory Container
Classpath container wizard page for the Simple Directory Container 

The IClasspathContainerPage implementation

The implementation of the classpath container page will extend the abstract WizardPage and build a page using some basic SWT widgets. If you do not have working knowledge of SWT and JFace, you may want to refer to Resources, where there is a link to an SWT introduction article. Since SWT and JFace are outside the scope of this tutorial, we will not cover the details of creating and handling the SWT widgets or implementing the JFace wizard page. The implementations of theIClasspathContainerPage and IClasspathContainerPageExtension, on the other hand, will be discussed.

Every classpath container page needs to implement the org.eclipse.jdt.ui.wizards.IClasspathContainerPageinterface. The org.eclipse.jdt.ui.wizards.IClasspathContainerPageExtension andorg.eclipse.jdt.ui.wizards.IClasspathContainerPageExtension2 can be optionally implemented. We will implementorg.eclipse.jdt.ui.wizards.IClasspathContainerPageExtension because its purpose is to provide additional context information when the container page is opened. Since we need to know the project to which the container will be added to determine the project relative directory, we need to implement this interface. We do not need to implementorg.eclipse.jdt.ui.wizards.IClasspathContainerPageExtension2, since it is only necessary when the container page wizard is used to create more than one container entry. Our page wizard will only create at most one container entry each time Add Library... is pressed. The name of the container page class in the sample code iscpcontainer.example.SimpleDirContainerPage. It's a good idea to read the Javadoc for the org.eclipse.jdt.ui.wizards.IClasspathContainerPage* interfaces before reading on.

Since we are extending WizardPage and implementing org.eclipse.jdt.ui.wizards.IClasspathContainerPage andorg.eclipse.jdt.ui.wizards.IClasspathContainerPageExtension2, the instance methods in our wizard page will be called in the following order when the user clicks Add Library... from the build path properties dialog:

1. SimpleDirContainerPage()
(constructor)
Calls the WizardPage super() constructor
2. initialize()
(inherited from IClasspathContainerPageExtension)
Sets the project for this container
3. setSelection()
(inherited from IClasspathContainerPage)
Sets the existing container entry path to be edited or does nothing if the user is creating a new entry
4. createControl()
(inherited from WizardPage)
Builds the SWT widgets
5. finish()
(inherited from IClasspathContainerPage)
Called when the user presses the Finish button; validates the input and returns true if valid or sets an appropriate error message if not
6. getSelection()
(inherited from IClasspathContainerPage)
Called after a successful (valid) Finish; returns the new classpath entry that is created from the user's input

If the user clicks Edit... from the build path properties dialog, the sequence above will be preceded by a call tocanUpdateClasspathContainer() on the classpath container initializer. And it will be followed by a call to the initializer'srequestClasspathContainerUpdate() method. See Implementing a classpath container initializer for more information about the initializer.


Preventing duplication of entries included in the container

Why do we need a filter?

If you've followed along, you should know how to create a handy, easy-to-use classpath container. The user opens the wizard from the build path properties to define a container that collects classpath entries from a Java project directory using a set of configured extensions.

As with all containers, the list of entries included in the container will show in the Java Package Explorer when the container twisty is expanded. There is still the possibility, however, that the user does not expand the container or does not look closely at the entries. In this case, he might unintentionally add a file to the classpath as a CPE_LIBRARY entry when the file is already included in the container. JAR files, for instance, can easily be added as CPE_LIBRARY entries by right-clicking the JAR and selecting Build Path > Add to Build Path (see Figure 5).

While having a duplicate classpath entry will not affect the runtime or compilation results, it does create unnecessary clutter in the .classpath file and it could lead to problems later if the user tries to remove the classpath entry.


Figure 6. Before adding JAR file to classpath as a CPE_LIBRARY entry
Before adding JAR file to classpath as a CPE_LIBRARY entry 

When a user right-clicks on a JAR file and selects Build Path > Add to Build Path, the JAR file is added to the classpath as aCPE_LIBRARY entry. As shown in Figure 7, the file is also filtered from view under the directory, since it is now visible at the root of the project as a CPE_LIBRARY classpath entry. In this section, we will add a Java element filter extension to our plug-in that filters files contained in the classpath container from showing in the Java Package Explorer as ordinary files.


Figure 7. After adding a JAR file to the classpath as a CPE_LIBRARY entry
After adding a JAR file to the classpath as a CPE_LIBRARY entry 

The org.eclipse.jdt.ui.javaElementFilters extension

As with any extension, we first need to add the extension to our plugin.xml. Open the plugin.xml in the PDE editor and click on theExtensions tab to add the org.eclipse.jdt.ui.javaElementFilters extension. Switch to the plugin.xml to edit the plugin.xml source. We need to add a filter element under the extension. Then we need to add some attributes to the filter element, starting with the ID of our filter, which can be anything but it should following the naming prefix we've used in the other extensions, cpcontainer.example. We will set the targetId of the view, where the filter will apply, which isorg.eclipse.jdt.ui.PackageExplorer for the Java Package Explorer. We will set the enabled flag to true to enable the filter when the plug-in is installed. The user will have the ability to disable the filter after installation.

Next, we provide the name and description for the filter that will show in the UI when the user is disabling/enabling filters. Finally, we provide the class that implements the filter, which must extend org.eclipse.jface.viewers.ViewerFilter. The full extension is shown in Listing 8.


Listing 8. The javaElementFilters extension
            <extension
            point="org.eclipse.jdt.ui.javaElementFilters">
            <filter
            id="cpcontainer.example.ContainerDirFilter"
            targetId="org.eclipse.jdt.ui.PackageExplorer"
            enabled="true"
            name="%ContainerFilterName"
            description="%ContainerFilterDesc"
            class="cpcontainer.example.ContainerDirFilter"/>
            </extension>
            

The ViewerFilter implementation

Now we need to implement a ViewerFilter class. As you can see from the class reference above, the one in the sample is calledcpcontainer.example.ContainerDirFilter. It simply extends the org.eclipse.jface.viewers.ViewerFilter abstract class and implements its only abstract method, select(). The select() method returns true if an element with a given parent element should pass through the filter (be visible) for the given viewer. Since our filter extension has a targetId set toorg.eclipse.jdt.ui.PackageExplorer, we will assume that we are only called in the case of the Java Package Explorer. So, in our select() implementation, we only need to determine if the element is included in our classpath container implementation. The select() method of the filter is shown below.


Listing 9. The select() method of ContainerDirFilter
            /*
            * @ return false if the Java element is a file that is contained in a
            * SimpleDirContainer that is in the classpath of the owning Java project
            * (non-Javadoc)
            * @see org.eclipse.jface.viewers.ViewerFilter#select(
            * org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
            */
            @Override
            public boolean select(Viewer viewer, Object parentElement, Object element) {
            if(element instanceof IFile) {
            IFile f = (IFile)element;
            IJavaProject jp = JavaCore.create(f.getProject());
            try {
            // lets see if this file is included in a SimpleDirContainer
            IClasspathEntry[] entries = jp.getRawClasspath();
            for(IClasspathEntry entry: entries) {
            if(entry.getEntryKind()==IClasspathEntry.CPE_CONTAINER) {
            if(SimpleDirContainer.ID.isPrefixOf(entry.getPath())) {
            // we know this is a SimpleDirContainer so lets get the instance
            SimpleDirContainer con = (SimpleDirContainer)JavaCore.
            getClasspathContainer(entry.getPath(), jp);
            if(con.isContained(f.getLocation().toFile())){
            // this file will is included in the container, so don't
            // show it
            return false;
            }
            }
            }
            }
            } catch(JavaModelException e) {
            Logger.log(Logger.ERROR, e);
            }
            }
            return true;
            }

For each element, we check if it is a file and, if so, we get the classpath entries for the containing Java project. Then, we iterate through the entry to see if there are any classpath containers that have a path with a first segment equal tocpcontainer.example.SIMPLE_DIR_CONTAINER. If so, we retrieve the container from the Java model and call the container'sisContained() instance method to see if the file is contained in the instance container. If it is contained, we return false, so the file is not shown outside of the container in the Java Package Explorer. Note that we have to check that each entry is a container with the correct prefix before calling the JavaCore.getClasspathContainer() method, so we are sure that the container already exists. If we call JavaCore.getClasspathContainer() on a nonexistent container, the Java model will create a new container instance and return it.

Detail link: 

http://www.ibm.com/developerworks/opensource/tutorials/os-eclipse-classpath/downloads.html

 

 

posted on 2010-09-07 17:37 Daniel 阅读(4916) 评论(0)  编辑  收藏 所属分类: Eclipse的相关

只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问  
 
<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(3)

随笔档案

文章分类

文章档案

相册

搜索

  •  

最新评论