Before you start
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.
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
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.
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.
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 apath
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
kind
s. In the underlying Eclipse Java model, each of these is represented by anorg.eclipse.jdt.core.IClasspathEntry
type, which categorizes the entry-by-entrykind
and contentkind
. A contentkind
is defined for most entrykind
s and is either source (org.eclipse.jdt.core.IPackageFragmentRoot.K_SOURCE
) or binary (IPackageFragmentRoot.K_BINARY
). In Eclipse V3.2, theIClasspathEntry
interface defines five constants to refer to the classpath entrykind
, but these are not the same five marked in Listing 1. In fact, one shown in this file — namelyoutput
— is not defined as a constant inIClasspathEntry
, and another —src
— is shown twice in Listing 1 because the two are represented as separate constants inIClasspathEntry
. This makes it a little confusing, but let's ignore that for now and start from the top of the file.The first entry in Listing 1 has
kind="src"
withpath="src"
. This indicates that the project has a source folder located in a directory relative to the project root calledsrc
. 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 contentkind
ofIPackageFragmentRoot.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 firstsrc
entry in Listing 1 has apath="src"
because the workspace default source folder was changed to src.The second entry in Listing 1 has
kind="lib"
withpath="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 optionalsourcepath
attribute, which refers to the location of the source attachment. Thelib kind
entry is represented by the entrykind
constantIClasspathEntry.CPE_LIBRARY
and has a contentkind
ofIPackageFragmentRoot.K_BINARY
.The third entry in Listing 1 has
kind="var"
withpath="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 tolib
entries, except that the first element in the path is evaluated before using it in the classpath. Another difference betweenvar
andlib
entries is thatlib
entries can reference an archive or a folder of classes, whereasvar
entries can only reference an archive. Thevar kind
is a binary type of entry represented by the constantIClasspathEntry.CPE_VARIABLE
and the contentkind
is undefined. So, you should never usegetContentKind()
when referring to an IClasspathEntry withkind="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.The fourth entry in Listing 1 has
kind="src"
withpath="/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 thekind
attribute is equal to"src"
, as it is withIClasspathEntry.CPE_SOURCE
above, this entry is represented by a different entrykind
constant,IClasspathEntry.CPE_PROJECT
, and it has a contentkind
ofIPackageFragmentRoot.K_SOURCE
. The only difference in the .classpath entry is that the path attribute is relative to the workspace, instead of the project.The fifth entry in Listing 1 has
kind="con"
withpath="org.eclipse.jdt.launching.JRE_CONTAINER"
. This type of entry is the subject of this tutorial. Thekind
constant for this entry isIClasspathEntry.CPE_CONTAINER
, and the contentkind
is undefined. A container entry is a logical reference to an implementation oforg.eclipse.jdt.core.IClasspathContainer
. The implementation aggregates a set of concrete entries, either of typeIClasspathEntry.CPE_LIBRARY
orIClasspathEntry.CPE_PROJECT
.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 entry Icon Entry kind Content kind <classpathentry kind="src" path="src"/> CPE_SOURCE K_SOURCE <classpathentry kind="lib" path="lib/derby.jar" sourcepath="lib/derby-src.jar"/> CPE_LIBRARY K_BINARY <classpathentry kind="var" path="ECLIPSE_HOME/startup.jar"/> CPE_VARIABLE undefined <classpathentry combineaccessrules="false" kind="src" path="/bar"/> CPE_PROJECT K_SOURCE <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> CPE_CONTAINER undefined
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 differentkind
s, 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 theIClasspathContainer
, 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.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 oforg.eclipse.jdt.core.IClasspathEntry
when asked. Before proceeding, you should read the Javadoc fororg.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 theaccept()
method of the inner FilenameFilter class. Let's start by looking at the constructor shown in Listing 2.
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 is
cpcontainer.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 the
getClasspathEntries()
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, anisValid()
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 bygetClasspathEntries()
to filter the file list.
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 theaccept()
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 singleCPE_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 basicCPE_LIBRARY
entries when the source has not already been attached by the user. Eclipse does not provide this behavior for free, however, when theCPE_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 thegetClasspathEntries()
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 theclasspathContainerInitializer
, 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.