All the Netbeans Plugins which tried to implement the Perforce client uses p4 command line tool. This is somehow difficult, because you have to manage the input and output of a command line. A lot of effort with no benefit. Because the perforce team put on place what is called P4Java, the Perforce Java API. You can find the release notes here (last version) http://www.perforce.com/perforce/doc.091/user/p4javanotes.txt. Download it from here. The main benefits are the fact that you work with objects, don't have to parse things, and to manage external processes. Thought, also, it is faster than its command line brother. Since this is not an NetBeans Platform thing, I will not insist on that too much.
As mentioned in a previous article, the presentation of versioning information is implemented in a class based on VCSAnnotator. In our case, this class is called PerforceAnnotator. There are some aspects which need to be explained. First is the fact that this class is a listener on some specific property change. That is done using the following code:
public class PerforceAnnotator extends VCSAnnotator {
/**
* Fired when textual annotations and badges have changed.
* The NEW value is Set<File> of files that changed or NULL
* if all annotaions changed.
*/
public static final String PROP_ANNOTATIONS_CHANGED = "annotationsChanged";
private final PropertyChangeSupport support = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
The PerforceAnnotator has two methods related to property change events: addPropertyChangeListener and removePropertyChangeListener. How it works? We define a logical property, named "annotationChanged". Any listener added will be notified if this property have been changed. This code works together with another piece from PerforceVS and another one from FileStatusCache. Practically, through this logical properties and wire of listeners we send messages from a component to another. They remain loosely coupled. As a sample, when the status of a file has been changed (from perforce point of view), the instance of FileStatusCache (which manages these statuses) notify PerforceVS that something on a file has been changed. PerforceVS than will notify the PerforceAnnotator about that change and the annotator will change also the status in UI (labels, colors, versioning info). The property change method is a very important concept. It is used very ofted to put UI things together, so take a closer lok on that, whenever you have some time.
PerforceAnnotator show some UI information about the status of files. The icon from explorer view is modified and the name and color of the file element is modified. These is done with the following code.
@Override
public Image annotateIcon(Image oldImage, VCSContext context) {
FileStatusCache cache = PerforceSystem.getCache();
int x = 12;
int y = 0;
if (cache.containsStatus(context, FileStatus.STATUS_VERSIONED_EDIT)) {
return ImageUtilities.mergeImages(oldImage, ImageRoot.DECORATION_EDIT, x, y);
}
if(cache.containsStatus(context, FileStatus.STATUS_VERSIONED_ADD)) {
return ImageUtilities.mergeImages(oldImage, ImageRoot.DECORATION_ADD, x, y);
}
if(cache.containsStatus(context, FileStatus.STATUS_VERSIONED_UPTODATE)) {
return super.annotateIcon(oldImage, context);
}
return super.annotateIcon(oldImage, context);
}
@Override
public String annotateName(String name, VCSContext ctx) {
FileStatusCache cache = PerforceSystem.getCache();
int version;
List<FileInformation> files;
files = cache.listFiles(ctx, FileStatus.STATUS_VERSIONED_EDIT);
if (files.size() > 0) {
version = files.get(0).getVersion();
return markName(name, "#0B610B", "#" + version + " [edit]");
}
files = cache.listFiles(ctx, FileStatus.STATUS_VERSIONED_ADD);
if (files.size() > 0) {
version = files.get(0).getVersion();
return markName(name, "#084B8A", "#" + version + " [add]");
}
files = cache.listFiles(ctx, FileStatus.STATUS_VERSIONED_UPTODATE);
if (files.size() > 0) {
version = files.get(0).getVersion();
return markName(name, null, "#" + version);
}
files = cache.listFiles(ctx, FileStatus.STATUS_UNKNOWN);
if (files.size() > 0) {
return super.annotateName(name, ctx);
}
if (name.equalsIgnoreCase("<default package>")) {
return "<default package>";
}
return super.annotateName(name, ctx);
}
private String markName(String name, String color, String label) {
boolean extra = VersioningSupport.getPreferences().getBoolean(
VersioningSupport.PREF_BOOLEAN_TEXT_ANNOTATIONS_VISIBLE, false);
if (color == null) {
return name + (extra ? label : "");
}
return "<font color=\"" + color + "\">" + name + "</font>" +
(extra ? " <font color=\"" + color + "\">" + label + "</font>" : "");
}
How it works? Depending on the status of the file, we modify the actual icon or label of the file from explorer view. To find the status of the file we ask the FileStatusCache, this class manages the FileInformation related to every managed file. The VCSContext represents the selection of files on which we should show information. Note that is possible to have multiple files in a context. In order to keep things simple, I considered that context have only one file and if there are many, I take into consideration only the first one. In time this should be changed. The same scenario happens for labels. On labels we use HTML tags because the view allow that to modify the aspect of a label.
One small tip to know. ImageUtilities class offers some very useful methods in managing images. We can use that class to merge two images, as is the case with PerforceAnnotator (we put a small Perforce icon over the original NetBeans file icon). We can load an image giving only a location in class path and we can transform with easy from an image into an icon and viceversa.
Let's take a look on the results of our work until now.
@Override
public Action[] getActions(VCSContext ctx, ActionDestination destination) {
List<Action> actions = new ArrayList<Action>();
FileStatusManager manager = FileStatusManager.getInstance();
switch (destination) {
case MainMenu:
actions.addAll(manager.getAvailableActionsOnMainMenu(ctx));
break;
case PopupMenu:
actions.addAll(manager.getAvailableActionsOnPopup(ctx));
break;
}
return actions.toArray(new Action[actions.size()]);
}
One more cookie, thought. To create a progress notification which will be displayed on status bar in a dedicated section, you can use ProgressHandle. Like in the sample below taken form FileStatusCache.reloadCacheOnThread.
final ProgressHandle progress =ProgressHandleFactory.createHandle("Perforce refresh..");progress.start();.....progress.finish();
I mention this simple thing because a lot of things in NetBeans are very simple and straightforward to implement or use. That is the meaning of strong API. The simplest form possible, intuitive and elegant. I think NetBeans Platform has a lot of those.
Untill the next time, see changes on http://kenai.com/projects/perforcenb.
Untill the next time, see changes on http://kenai.com/projects/perforcenb.