Die beste Art, um ein Verständnis für Synchronisierungs-APIs zu bekommen, ist die Erstellung eines einfachen, funktionierenden Beispiels. In diesem Beispiel wird eine Seite in der Sicht 'Synchronisieren' erstellt, auf der der aktuelle Status des lokalen Protokolls aller Dateien im Arbeitsbereich angezeigt wird. Die Synchronisierung der lokalen Protokolle wird automatisch aktualisiert, wenn Änderungen am Arbeitsbereich durchgeführt werden. In einem Vergleichseditor können die Änderungen durchsucht und zusammengefügt werden. Darüber hinaus wird ein angepasster Dekorator eingefügt, der die letzte Zeitmarke des lokalen Protokollelements anzeigt, sowie eine Aktion, über die Dateien im Arbeitsbereich auf den jeweils letzten gespeicherten Status des lokalen Protokolls zurückgesetzt werden können. Der Vorteil dieses Beispiels besteht darin, dass gespeicherte Ressourcenvarianten bereits zur Verfügung stehen, die nicht verwaltet werden müssen.
Für den Rest dieses Beispiels wird ein laufendes Beispiel verwendet. Ein großer Teil des Quellcodes (allerdings nicht alles) ist auf dieser Seite enthalten. Der vollständige Quellcode kann im Paket 'Lokales Protokoll' des Plug-ins org.eclipse.team.examples.filesystem gefunden werden. Sie können das Projekt aus dem CVS-Repository entnehmen und während dieses Lernprogramms als Referenz verwenden. Hinweis: Der Quellcode der Beispiel-Plug-ins kann sich im Laufe der Zeit ändern. Um eine Kopie zu erhalten, die diesem Beispiel entspricht, können Sie das Projekt mit der Kennung der Version 3.0 (wahrscheinlich R3_0) oder einer Datumskennung vom 28. Juni 2004 entnehmen.
Dieser Screenshot stellt die Synchronisierung des lokalen Protokolls in der Sicht 'Synchronisieren' dar. Hierüber können Sie die Änderungen zwischen der lokalen Ressource und dem letzten Protokollstatus durchsuchen. Ein angepasster Dekorator zeigt die Zeitmarke des Eintrags des lokalen Protokolls an, und über eine angepasste Aktion können Sie die Datei auf die Inhalte des lokalen Protokoll zurücksetzen. Beachten Sie auch, dass die Standarddarstellung der Sicht 'Synchronisieren' verwendet wird, in der Problemanmerkungen, ein komprimiertes Ordnerlayout und Navigationsschaltflächen enthalten sind.
In einem ersten Schritt wird eine Variante definiert, um die Elemente des lokalen Protokolls darzustellen. Hierüber können die Synchronisierungs-APIs auf die Inhalte des lokalen Protokolls zugreifen, um sie mit den aktuellen Inhalten zu vergleichen und dem Benutzer anzuzeigen.
public class LocalHistoryVariant implements IResourceVariant {
private final IFileState state;
public LocalHistoryVariant(IFileState state) {
this.state = state;
}
public String getName() {
return state.getName();
}
public boolean isContainer() {
return false;
}
public IStorage getStorage(IProgressMonitor monitor) throws TeamException {
return state;
}
public String getContentIdentifier() {
return Long.toString(state.getModificationTime());
}
public byte[] asBytes() {
return null;
}
}
Dies war nicht sonderlich schwierig, da die Schnittstelle "IFileState" bereits Zugriff auf die Inhalte der Datei aus dem lokalen Protokoll bietet (d. h. sie implementiert die Schnittstelle "IStorage").
Generell müssen Sie bei der Erstellung einer Variante einen Zugriffsmöglichkeit auf den Inhalt bereitstellen, sowie eine Inhaltskennung, die dem Benutzer zur Identifizierung dieser Variante angezeigt wird, sowie einen Namen. Die Methode "asBytes()" ist nur erforderlich, wenn die Variante über mehrere Sitzungen hinweg bestehen bleiben soll.
Als nächstes wird eine Variantenvergleichsfunktion erstellt, über die die SyncInfo-Berechnung
lokale Ressourcen mit ihren Varianten vergleichen kann. Auch dies ist nicht schwierig, da die Existenz eines lokalen Protokolls bereits impliziert, dass sich der Inhalt des Status des lokalen Protokolls von den aktuellen Inhalten der Datei unterscheidet. Grund hierfür ist die Spezifikation des lokalen Protokolls, die besagt, dass kein Status des lokalen Protokolls erstellt wird, wenn sich die Datei nicht geändert hat.
public class LocalHistoryVariantComparator implements IResourceVariantComparator {
public boolean compare(IResource local, IResourceVariant remote) {
return false;
}
public boolean compare(IResourceVariant base, IResourceVariant remote) {
return false;
}
public boolean isThreeWay() {
return false;
}
}
Da bereits bekannt ist, dass die Existenz eines lokalen Protokollstatus einen Unterschied zur lokalen Ressource impliziert, kann der Einfachheit halber der Wert false zurückgegeben werden, wenn die Datei mit ihrem lokalen Protokollstatus verglichen wird. Darüber hinaus wird die Synchronisierung mit dem lokalen Protokoll nur deswegen in beide Richtungen ausgeführt, weil wir keinen Zugriff auf eine Basisressource haben und die Methode für den Vergleich zweier Ressourcenvarianten nicht verwendet werden kann.
Beachten Sie, dass die Synchronisierungsberechnung die Vergleichsmethode der Vergleichsfunktion nicht aufruft, wenn die Variante nicht existiert (i.e. gleich 'null' ist). Sie wird nur aufgerufen, wenn beide Elemente existieren. Im Beispiel trifft dies sowohl auf Dateien ohne lokales Protokoll als auch auf Ordner (die niemals ein lokales Protokoll haben) zu. Eine eigene Unterklasse von SyncInfo muss definiert werden, um hiermit umzugehen und den berechneten Synchronisierungsstatus dieser Klassen zu ändern.
public class LocalHistorySyncInfo extends SyncInfo {
public LocalHistorySyncInfo(IResource local, IResourceVariant remote, IResourceVariantComparator comparator) {
super(local, null, remote, comparator);
}
protected int calculateKind() throws TeamException {
if (getRemote() == null)
return IN_SYNC;
else
return super.calculateKind();
}
}
Der Konstruktor wurde überschrieben, so dass er immer eine Basis mit dem Wert null bereitstellt (da nur Vergleiche in zwei Richtungen durchgeführt werden) und die Berechnung der Synchronisierungsart wurde so geändert, dass sie IN_SYNC zurückgibt, wenn es kein 'remote' gibt (da nur die Fälle interessant sind, in denen es sowohl eine lokale Datei als auch einen Dateistatus im lokalen Protokoll gibt).
Als nächstes wird ein Subskribent erstellt, der einen Zugriff auf die Ressourcenvarianten im lokalen Protokoll ermöglicht. Da das lokale Protokoll in jeder beliebigen Datei im Arbeitsbereich gespeichert werden kann, überwacht der Subskribent für das lokale Protokoll alle Ressourcen, und die Gruppe der Stammelemente umfasst alle Projekte des Arbeitsbereichs. Der Subskribent muss auch nicht aktualisierbar sein, da sich das lokale Protokoll nur dann ändert, wenn die Inhalte einer lokalen Datei geändert werden. Daher kann der Status immer aktualisiert werden, wenn ein Ressourcendelta auftritt. Somit bleiben nur noch zwei interessante Methoden für den Subskribenten des lokalen Protokolls: SynchInfo abrufen und den Arbeitsbereich traversieren.
public SyncInfo getSyncInfo(IResource resource) throws TeamException {
try {
IResourceVariant variant = null;
if(resource.getType() == IResource.FILE) {
IFile file = (IFile)resource;
IFileState[] states = file.getHistory(null);
if(states.length > 0) {
// last state only
variant = new LocalHistoryVariant(states[0]);
}
}
SyncInfo info = new LocalHistorySyncInfo(resource, variant, comparator);
info.init();
return info;
} catch (CoreException e) {
throw TeamException.asTeamException(e);
}
}
Der Subskribent gibt ein neues Exemplar von SyncInfo zurück, das den aktuellsten Status der Datei im lokalen Protokoll enthält. SyncInfo wird für das ferne Element mit einer Variante des lokalen Protokoll erstellt. Für Projekte, Ordner und Dateien ohne lokales Protokoll wird keine ferne Ressourcenvariante bereitgestellt, wodurch die Ressource aufgrund der Methode calculateKind in "LocalHistorySyncInfo" als synchron betrachtet wird.
Der verbleibende Code im Subskribent des lokalen Protokolls ist die Implementierung der Methode members:
public IResource[] members(IResource resource) throws TeamException {
try {
if(resource.getType() == IResource.FILE)
return new IResource[0];
IContainer container = (IContainer)resource;
List existingChildren = new ArrayList(Arrays.asList(container.members()));
existingChildren.addAll(
Arrays.asList(container.findDeletedMembersWithHistory(IResource.DEPTH_INFINITE, null)));
return (IResource[]) existingChildren.toArray(new IResource[existingChildren.size()]);
} catch (CoreException e) {
throw TeamException.asTeamException(e);
}
}
Ein interessantes Detail dieser Methode ist die Tatsache, dass es nicht existierende untergeordnete Elemente zurückgibt, wenn eine gelöschte Ressource über ein lokales Protokoll verfügt. Hierdurch kann der Subskribent SyncInfo für Elemente zurückgeben, die nur im lokalen Protokoll existieren und sich nicht länger im Arbeitsbereich befinden.
Bisher wurde Klassen erstellt, die Zugriff auf SyncInfo für Elemente im lokalen Protokoll bieten. Als nächstes werden die Benutzerschnittstellenelemente erstellt, über die eine Seite in der Sicht 'Synchronisieren' erstellt werden kann, die für jedes Element des lokalen Protokolls den letzten Protokollstatus anzeigt. Aufgrund des Subskribenten ist es nicht schwer, diese Information zur Sicht 'Synchronisieren' hinzuzufügen. Zunächst wird ein Erweiterungspunkt für einen Synchronisierungsteilnehmer hinzugefügt:
<extension point="org.eclipse.team.ui.synchronizeParticipants">
<participant
persistent="false"
icon="synced.png"
class="org.eclipse.team.synchronize.example.LocalHistoryParticipant"
name="Latest From Local History"
id="org.eclipse.team.synchronize.example"/>
</extension>
Als nächstes wird "LocalHistoryParticipant" implementiert. Dies ist eine Unterklasse von "SubscriberParticipant" und bietet das Standardverhalten, das benötigt wird, um SyncInfo vom Subskribenten zu erfassen und den Synchronisierungsstatus bei Änderungen am Arbeitsbereich zu aktualisieren. Darüber hinaus wird eine Aktion hinzugefügt, über die Arbeitsbereichressourcen auf den letzten Status im lokalen Protokoll zurückgesetzt werden können.
Zunächst wird eine angepasste Aktion zum Teilnehmer hinzugefügt.
public static final String CONTEXT_MENU_CONTRIBUTION_GROUP = "context_group_1"; //$NON-NLS-1$
private class LocalHistoryActionContribution extends SynchronizePageActionGroup {
public void initialize(ISynchronizePageConfiguration configuration) {
super.initialize(configuration);
appendToGroup(
ISynchronizePageConfiguration.P_CONTEXT_MENU, CONTEXT_MENU_CONTRIBUTION_GROUP,
new SynchronizeModelAction("Revert to latest in local history", configuration) { //$NON-NLS-1$
protected SynchronizeModelOperation getSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) {
return new RevertAllOperation(configuration, elements);
}
});
}
}
An dieser Stelle werden eine spezielle Aktion "SynchronizeMoidelAction" und ein Verarbeitungsschritt hinzugefügt. Hierdurch wird als Verhalten die Fähigkeit erzielt, im Hintergrund zu laufen und für die Knoten, die gerade bearbeitet werden, den Status 'besetzt' anzuzeigen. Die Aktion setzt alle Ressourcen im Arbeitsbereich auf ihren letzten Status im lokalen Protokoll zurück. Die Aktion wird durch eine Aktionsergänzung der Teilnehmerkonfiguration hinzugefügt. Die Konfiguration dient zur Beschreibung der Eigenschaften, die die Teilnehmerseite erstellen, auf der die eigentliche Synchronisierungsbenutzerschnittstelle angezeigt wird.
Der Teilnehmer initialisiert die Konfiguration folgendermaßen, um die Aktionsgruppe des lokalen Protokolls zum Kontextmenü hinzuzufügen:
protected void initializeConfiguration(ISynchronizePageConfiguration configuration) {
super.initializeConfiguration(configuration);
configuration.addMenuGroup(
ISynchronizePageConfiguration.P_CONTEXT_MENU,
CONTEXT_MENU_CONTRIBUTION_GROUP);
configuration.addActionContribution(new LocalHistoryActionContribution()); configuration.addLabelDecorator(new LocalHistoryDecorator());
}
Als nächstes wird ein angepasster Dekorator hinzugefügt. Die letzte Zeile der voranstehenden Methode registriert den folgenden Dekorator mit der Seitenkonfiguration.
public class LocalHistoryDecorator extends LabelProvider implements ILabelDecorator {
public String decorateText(String text, Object element) {
if(element instanceof ISynchronizeModelElement) {
ISynchronizeModelElement node = (ISynchronizeModelElement)element;
if(node instanceof IAdaptable) {
SyncInfo info = (SyncInfo)((IAdaptable)node).getAdapter(SyncInfo.class);
if(info != null) {
LocalHistoryVariant state = (LocalHistoryVariant)info.getRemote();
return text+ " ("+ state.getContentIdentifier() + ")";
}
}
}
return text;
}
public Image decorateImage(Image image, Object element) {
return null;
}
}
Der Dekorator extrahiert die Ressource aus dem Modellelement, das in der Sicht 'Synchronisieren' erscheint, und fügt die Inhaltskennung der Ressourcenvariante des lokalen Protokolls zur Textkennung hinzu, die in der Sicht angezeigt wird.
Zum Schluss muss noch ein Assistent bereitgestellt werden, der den Teilnehmer des lokalen Protokolls erstellt. Die Perspektive 'Teamsynchronisierung' definiert eine globale Synchronisierungsaktion, über die Benutzer schnell eine Synchronisierung erstellen können. Darüber hinaus steht die Fähigkeit, Synchronisierungen zu erstellen, in der Synchronisierungssymbolleiste zur Verfügung. Zu Beginn wird ein Erweiterungspunkt "synchronizeWizards" erstellt:
<extension
point="org.eclipse.team.ui.synchronizeWizards">
<wizard
class="org.eclipse.team.synchronize.example.LocalHistorySynchronizeWizard"
icon="synced.png"
description="Creates a synchronization against the latest local history state of all resources in the workspace"
name="Latest From Local History Synchronize"
id="ExampleSynchronizeSupport.wizard1"/>
</extension>
Hierdurch wird der Assistent zur Liste hinzugefügt. In der Methode finish() erstellen wir nun einfach den Teilnehmer und fügen ihn zum Synchronisierungsmanager hinzu.
LocalHistoryPartipant participant = new LocalHistoryPartipant();
ISynchronizeManager manager = TeamUI.getSynchronizeManager();
manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant});
ISynchronizeView view = manager.showSynchronizeViewInActivePage();
view.display(participant);
Dieses einfache Beispiel der Verwendung von Synchronisierungs-API hat der Verständlichkeit halber ein paar Details übersprungen.
Die Erstellung einer reaktionsfähigen und akkuraten Synchronisierungsunterstützung ist nicht trivial. Der schwierigste Teil besteht in der Verwaltung von Synchronisierungsinformationen und der Benachrichtigung über Änderungen im Synchronisierungsstatus. Die Benutzerschnittstelle ist einfach (vorausgesetzt, dass diejenige, die "SubscriberParticipants" zugeordnet wird, geeignet ist), sobald die Implementierung des Subskribenten abgeschlossen ist. Weitere Beispiele finden Sie im Plug-in "org.eclipse.team.example.filesystem"
und in den Unterklassen von "Subscriber" und "ISynchronizeParticipant" im Arbeitsbereich.
Im nächsten Abschnitt werden Klassen und Schnittstellen beschrieben, die Sie bei der Erstellung eines Subskribenten von Grund auf unterstützen. Hierunter fällt auch die Zwischenspeicherung von Synchronisierungsstatuswerten zwischen Workbench-Sitzungen.