A complete implementation of the VCS API is presented in the
SampleVCS
OpenTools sample. The code can be found in the
samples directory and can be used as a guideline to integrate any VCS
into JBuilder taking advantage of the History pane and the VCS Commit
Dialog. This document uses snippets of code from the
SampleVCS
class to illustrate examples of implementation
of the VCS API.
com.borland.primetime.teamdev
tree. They are:
com.borland.primetime.teamdev.vcs.VCS
.
This interface defines the API required to register and integrate the
VCS into JBuilder. It consists of just 16 methods to make your work as
easy as possible. Some of these methods are used to define a series of
actions that will be accessible with the Team menu. The scope of
these actions is totally under your control and there are no specific
constraints or hierarchies to follow. In other words, if the
implementer of a VCS back-end must display a dialog box (to administer
access rights, for example) the action can define any UI element and
execute any action without having to fit into a specific
framework. The usual OpenTools and Swing guidelines apply. You can
access any feature of the selected VCS without having to adhere to a
minimum common denominator.
com.borland.primetime.vcs.VCSFactory
). For example:
package com.borland.samples.samplevcs; import com.borland.primetime.teamdev.vcs.*; public class SampleVCS implements VCS { public static void initOpenTool( byte major, byte minor ) { // VCS support was added starting from JBuilder 4 if ( major < 4) { return; } VCSFactory.addVCS(new SampleVCS()); } . . . public String getName() { return "SampleVCS"; } }This makes the VCS selectable in the Team page in the Project Properties dialog box. Note that the
getName()
method returns the name that will be associated with the VCS. This is the result in the Team property page.
![]() |
![]() |
The SampleVCS is added to the list of registered VCSs | The result of calling VCS.getProjectConfigPage()
|
To provide a configuration panel to the user, define a
subclass of com.borland.primetime.properties.PropertyPage
(basically a JPanel
). In our example, the page has only a
text field to enter a directory name and a button to set this value
using JBuilder's
com.borland.primetime.vfs.ui.UrlChooser
. JBuilder obtains
this page by calling the VCS.getProjectConfigPage()
method.
Save VCS settings together with the project to make them persistent. You can do this using the com.borland.jbuilder.node.JBProject.setAutoProperty()
method. The method accepts three parameters. The first is the
category for the property. It should always be
VCS.CATEGORY
. The second parameter is the name of the
property. This name should be unique so that it doesn't conflict with
other VCSs. It's a good idea to use the name of the VCS to prefix the
property name (for example, SampleVCS_path). The last parameter is the
property's value. Here is how SampleVCS.ConfigPage
saves
and restores its properties:
public void writeProperties() { project.setAutoProperty(VCS.CATEGORY,SampleVCS.PROP_PATH,pnlConfig.getPath()); } public HelpTopic getHelpTopic() { return null; } public void readProperties() { String path = project.getAutoProperty(VCS.CATEGORY,SampleVCS.PROP_PATH); if ( path != null ) { pnlConfig.setPath(path); } }
getVCSContextMenuGroup()
method is called by JBuilder
to retrieve a list of Actions that can be displayed when the user
right-clicks on a file that is under the VCS. The method returns an
ActionGroup
initialized with all the actions that can be
available when right-clicking on files under the given VCS. If the
method return null
then no menu will be displayed.
Here is how the SampleVCS exports its context menu:
public ActionGroup getVCSContextMenuGroup() { ActionGroup aGroup = new ActionGroup("SampleVCS",'S',"Manage file with the Sample VCS",null,true ); aGroup.add(Actions.ADD); aGroup.add(Actions.CHECKIN); aGroup.add(Actions.GET_LOG); return(aGroup); }The
Actions
class defines the static Action objects used
by several methods of the SampleVCS
class:
public class Actions { . . . public static UpdateAction ADD = new UpdateAction("Add",'A',"Add the selected file(s) to the VCS") { public void update( Object source ) { // compute the enabled status based on the type of nodes selected } public void actionPerformed( ActionEvent e ) { Node[] nodes = Browser.getActiveBrowser().getProjectView().getSelectedNodes(); SampleVCS vcs = (SampleVCS)VCSFactory.getVCS("SampleVCS"); int len = nodes.length; for( int i=0; i <len; i++ ) { File workFile = ((FileNode)nodes[i]).getUrl().getFileObject(); vcs.addFile(workFile); } } }; public static UpdateAction CHECKIN = new UpdateAction("Checkin",'C',"Commit changes to the repository") { public void update( Object source ) { setEnabled(getEnabledState()); } public void actionPerformed( ActionEvent e ) { . . . } }; public static UpdateAction GET_LOG = new UpdateAction("See log",'L',"See the comments for changes checked in") { public void update( Object source ) { setEnabled(getEnabledState()); } public void actionPerformed( ActionEvent e ) { . . . } }; };
VCS.getRevisions()
method to retrieve a Vector
of RevisionInfo
for a specified file. The
method receives an Url
(the JBuilder version, not the one
in the java.net
package) and fills the Vector
with
all the revisions found for the file. The RevisionInfo
class is designed to report revision information in a VCS-neutral way
(at least that was the goal). Here is an example:
public Vector getRevisions(Url url) { Vector revs = new Vector(); File repo = getFileInRepository(url.getFileObject()).getParentFile(); final String fname = url.getName(); String[] list = repo.list( new FilenameFilter() { public boolean accept( File dir, String name ) { return name.startsWith(fname) && (! name.endsWith(".comment")); } }); int len = list.length; String revNumber; for( int i=0; i < len; i++ ) { revNumber = list[i].substring(list[i].lastIndexOf(',')+1); try { BufferedReader reader = new BufferedReader(new FileReader(new File(repo,list[i]+".comment"))); String desc = reader.readLine(); String who = reader.readLine(); String time = reader.readLine(); String label = reader.readLine(); GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT")); cal.setTime(new Date(Long.parseLong(time))); cal.setTimeZone(TimeZone.getDefault()); RevisionInfo r = new RevisionInfo(revNumber,cal.getTime().getTime(),who,desc, label); if ( getCurrentRevision(url.getFileObject()).equals(revNumber) ) { r.setWorkingRevision(true); } revs.add(r); } catch (Exception ex) { ex.printStackTrace(); } } // The VCSCommitBrowser assumes that the first element has the newest revision Collections.reverse(revs); return revs; }As the example demonstrates, the parameters passed to
RevisionInfo
are the revision number, the time of the
revision (in this case time is expressed using the GMT time zone), the name of
the user that checked in the revision, the description of the changes,
and the version label. The revision number is expressed here as a
String, but it's actually more complex than that. There's a class
designed to represent version numbers in a way that is more suitable
for sorting than using Strings. See
com.borland.primetime.teamdev.vcs.AbstractRevisionNumber
,
com.borland.primetime.teamdev.vcs.NumericRevisionNumber
, and
com.borland.primetime.teamdev.vcs.StringRevisionNumber
.
Note that care is taken to identify the revision that was used to
check out the file in consideration. Depending on the VCS, you can obtain this value
in a variety of ways. You can notify JBuilder of this conditions using the
RevisionInfo.setWorkingRevision(boolean)
method. From the
previous example:
if ( getCurrentRevision(url.getFileObject()).equals(revNumber) ) { r.setWorkingRevision(true); }
VSCCommitBrowser
is designed to provide a project-wide
view of the changes and to provide one-click access to the Visual Diff
Engine, both for changes done to the workspace, and to see what
happened in the repository if the VCS, like CVS, allows concurrent
updates.
The VCSCommitBrowser
class is part of the
com.borland.primetime.teamdev.frontend
package and can be
called using the
vcs.VCSUtils.showProjectStatus(CommitAction checkinAction)
method.
This method calls in turn the
vcs.VCS.getProjectStatus(Project project)
method. The
return value from getProjectStatus
is a
java.util.List
filled with instances of
vcs.VCS.VCSFileInfo
. VCSFileInfo
is a simple
storage class used to return status information about a file managed
by a VCS. The purpose of VCSFileInfo
is to simply hold
some file information since the VCSCommitBrowser
can display data
about files that are not directly under control of JBuilder's
projects. The class keeps a flag that can be used by the VCS
implementation to check if the file has to be committed. This flag is
set or cleared by clicking the Exec check box in the
VCSCommitBrowser
. Perhaps the most important component of the
VCSFileInfo
class is the status field. It is of type
vcs.VCSFileStatus
, an abstract class that must be
subclassed by any VCS implementation that needs to use the VCSCommitBrowser
.
Subclassing VCSFileStatus
provides these advantages:
Icon
with the description.
Stock Icons are provided by JBuilder in the
frontend.VCSIcons
class.
Here is an example from SampleVCS
:
public class RevisionStatus extends VCSFileStatus { public static final int STATUS_NEW = 0; public static final int STATUS_WSP_CHANGE = 1; public static final int STATUS_REPO_CHANGE = 2; public static final int STATUS_REMOVED = 3; private class FileDesc { public Icon icon; public String desc; public FileDesc( Icon icon, String desc ) { this.icon = icon; this.desc = desc; } } private FileDesc[] descs = { new FileDesc(VCSIcons.FILE_NEW, "New !" ), new FileDesc(VCSIcons.WORKSPACE_CHANGE, "Changed in workspace"), new FileDesc(VCSIcons.REPOSITORY_CHANGE,"Changed in repository"), new FileDesc(VCSIcons.FILE_REMOVED, "Removed !") }; public RevisionStatus( int status ) { this.status = status; } public ActionGroup getActions() { // We don't need no stinkin' actions... return null; } /** * Returns the icon that will be displayed in the VCSCommitBrowser */ public Icon getStatusIcon() { return descs[status].icon; } /** * called by VCSCommitBrowser to determine if the file has repository changes */ public boolean isModifiedInVCS() { return status == STATUS_REPO_CHANGE; } public String getDescription() { return descs[status].desc; } public boolean isModifiedLocally() { return status == STATUS_WSP_CHANGE; } }Note the definition of the methods
isModifiedInVCS()
and
isModifiedLocally()
. The VCSCommitBrowser
uses these to determine what kind of Diff tab must displayed and how to retrieve the revisions for the Visual Diff. Refer to the SampleVCS code (the Actions
class ) to see
how this plugs into the JBuilder menu system.
The
vcs.CommitAction
class is a subclass of Swing's
Action
, with two methods to report error conditions. The
action is called when the user chooses OK in the
VCSCommitBrowserDialog
. The idea behind it is that the
action will scan all the VCSFileInfos
that are set to be
committed and it will perform the appropriate action based on the
specific VCS rules.
In conclusion, the VCSCommitBrowser
and related classes
provides a consistent look-and-feel to analyze what changes need to be
committed to the VCS repository. You have a minimal
amount of work to do to provide this effective UI
element. The most complicated bit of work is the implementation of the
CommitAction
.