This is the last article about the Perforce client. That does not mean that is fully implemented, niether I will abandon the project. The NetBeans Perforce client will continue to be developed. By me, and hopefully by others. The point with this string of articles it to illustrate in which way it can be implemented a versioning client in NetBeans.
Since last article, the Perforce client was enriched with many functionalities. Thought, only the last one is relevant for our purpose. The topic is how to integrate into Perforce the IDE file manipulation operations like add, delete, rename, move or edit. You can add here also, how to handle the same situations, when files were modified outside IDE.
The key to this functionality is a class called VCSInterceptor. This class is used by NetBeans to announce the eventually versioning system about some change over files. Either if the change was operated form inside or outside of IDE.
I will split the methods from VCSInterceptor into five categories: queries, delete, move, create and change. You can find the source (comments are very intereting to read) here. Take a close look there.
queries
This category contain only one method isMutable(File file). This method is used to ovveride the default behavior proposed by the versioning systems which uses read-only files. When a file is read only, the IDE will see the file in this state, so it will not edit it. But when this method returns true, you let the IDE know that you actually can edit the file, even if is marked as read-only from the file system.
delete
beforeDelete and doDelete work together. The first let the IDE know if you want to implement the delete action for the specified file, in the second method you actually write the delete operation. There is another one method, afterDelete. This method is called after the file was deleted from the files system. Besides the moment of the notification, there is another very important difference. All methods are called when a delete operation was realized from IDE, but only the later (afterDelete) is fired when a file was deleted outside IDE. Take this into consideration when you implement the delete handling.
move
beforeMove, doMove, afterMove. Follows the same pattern as for delete operation. beforeMove is used to tell to the IDE that is want or don't want to handle the file move. doMove implements the real move action and afterMove is called (hard to believe, but ) .. after the mov operation. The only difference from delete is that all operations are called only for IDE file rename/move opertions. For this kind of operations outside IDE, NetBeans will fire two events: afterDelete for the source and afterCreate for the target.
create
Following the same pattern, for create we have beforeCreate, doCreate and afterCreate. Adding new files from outside IDE will fire only the last event (like on delete operations). I will go on, I hate to repeat.
change
This breaks the pattern. There is an beforeEdit method called when a file is about to be opened in edit mode. The is a perfect moment to do a checkout if you are in IDE. After that we have two events, beforeChange and afterChange. These events are related to content. beforeChange is called before the file content are about to be changed. afterChange end the cycle. One thing to mention here is that only afterChange is called if the file is updated outside IDE (javadoc does not mention that, but you can trust me on that). The later is a perfect place to put a checkout for outside IDE file modifications.
Just before presenting the source code for that, I have to mention that you don't need to implement all methods. These methods tries to catch all the possible events for all the possible versioning system scenarios. In my case i found to be enough to implement only 7 from 13. And I hope I covered all.
Here is the interceptor code:
public class PerforceInterceptor extends VCSInterceptor {
@Override
public boolean isMutable(File file) {
return true; // really, for all? we will see that
}
/**
* Automatically add to perforce the new added file.
* The code is here to handle also the files added from outside IDE.
*
* @param file file in question
*/
@Override
public void afterCreate(File file) {
if (PerforceModuleConfig.getInstance().isPerforceExcluded(file)) {
return;
}
FileStatus status = getUpdatedFileStatus(file);
if (status == null || FileStatus.STATUS_UNKNOWN.equals(status)) {
try {
// this is what we really care
P4Client p4client = PerforceSystem.getP4Client();
p4client.actionAdd(file);
} catch (PerforceActionException ex) {
Exceptions.printStackTrace(ex);
}
}
}
/**
* Called when a file is uptodate and is about to be modified.
* The file is automatically checked out.
*
* @param file file in question
*/
@Override
public void beforeEdit(final File file) {
if (PerforceModuleConfig.getInstance().isPerforceExcluded(file)) {
return;
}
FileStatus status = getUpdatedFileStatus(file);
if (!FileStatus.STATUS_VERSIONED_UPTODATE.equals(status)) {
return;
}
try {
P4Client p4client = PerforceSystem.getP4Client();
p4client.actionEdit(file);
} catch (PerforceActionException ex) {
Exceptions.printStackTrace(ex);
}
}
/**
* Check out the uptodate file. The method is called when the file was
* modified outside IDE.
* Same logic as {@link #beforeEdit(java.io.File) }.
*
* @param file file in question
*/
@Override
public void afterChange(File file) {
beforeEdit(file);
}
/**
* Mark for delete files which are delete from outside/inside IDE.
* Handle different scenarios depending on the status of the file.
*
* @param file file in question
*/
@Override
public void afterDelete(File file) {
try {
P4Client p4client = PerforceSystem.getP4Client();
FileStatus status = getUpdatedFileStatus(file);
if (status == null) {
return;
}
switch (status) {
case STATUS_VERSIONED_ADD:
p4client.actionRevert(file);
if (file.exists()) {
file.delete();
}
break;
case STATUS_VERSIONED_UPTODATE:
p4client.actionDelete(file);
break;
case STATUS_VERSIONED_EDIT:
p4client.actionRevert(file);
if (file.exists()) {
p4client.actionDelete(file);
}
break;
}
} catch (PerforceActionException ex) {
Exceptions.printStackTrace(ex);
}
}
@Override
public boolean beforeMove(File file, File file1) {
if (PerforceModuleConfig.getInstance().isPerforceExcluded(file)) {
return false;
}
return true;
}
@Override
@SuppressWarnings("fallthrough")
public void doMove(File source, File target) throws IOException {
try {
P4Client p4client = PerforceSystem.getP4Client();
FileStatus status = getUpdatedFileStatus(source);
if (status == null) {
return;
}
switch (status) {
case STATUS_VERSIONED_UPTODATE:
p4client.actionEdit(source);
case STATUS_VERSIONED_ADD:
case STATUS_VERSIONED_EDIT:
p4client.actionMove(source, target);
}
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
}
/**
* Retrieves the file status. If the cache is not hit, we do it the hard
* way, we push in cache the value from perforce system.
*
* @param file file in question
* @return the status of the file or null if the file is not handled at all.
*/
private FileStatus getUpdatedFileStatus(File file) {
FileStatusCache cache = PerforceSystem.getCache();
FileInformation fileInfo = cache.getFileInfo(file);
if (fileInfo == null) {
cache.refreshFiles(new String[]{file.getAbsolutePath()});
fileInfo = cache.getFileInfo(file);
}
return (fileInfo == null) ? null : fileInfo.getStatus();
}
}
As I said, foolow the source code and the project PerforceNB on kenai site at http://kenai.com/projects/perforcenb/. See you.
No comments:
Post a Comment