Voorbeeld van de lokale historie

U zult synchronisatie-API's het best kunnen begrijpen aan de hand van een eenvoudig voorbeeld. In dit voorbeeld maakt u een pagina in de view Synchroniseren waarmee de nieuwste lokale-historiestatus wordt afgebeeld van alle bestanden in het werkgebied. De historiesynchronisatie wordt automatisch bijgewerkt zodra wijzigingen worden aangebracht in het werkgebied. Verder kunt u een vergelijkingseditor openen voor het bekijken, samenvoegen en wijzigen van historiebestanden. In dit voorbeeld wordt bovendien een aangepaste decorator toegevoegd voor het afbeelden van de laatste datum/tijd van het element uit de lokale historie, alsmede een actie voor het terugzetten van de bestanden uit het werkgebied naar de laatste opgeslagen status uit de lokale historie. De toepassing is erg praktisch: We beschikken namelijk al over een reeks resourcevarianten, die we op deze manier niet hoeven te beheren.

Het vervolg van dit zelfstudiedocument omvat een doorlopend voorbeeld. Een groot gedeelte van de broncode kunt u op deze pagina vinden. De volledige broncode kunt u vinden in het pakket voor de lokale historie van de plugin org.eclipse.team.examples.filesystem. U kunt het project uit de CVS-repository reserveren en als naslag gebruiken tijdens het lezen van deze zelfstudie. Let op: De broncode in de voorbeeldplugins kan van tijd tot tijd worden gewijzigd. Als u een exemplaar wilt bemachtigen dat met dit voorbeeld overeenkomt, kunt u het project reserveren met de 3.0-versietag (waarschijnlijk R3_0) of de datumtag 28 juni, 2004.

overzicht van de lokale historie

Op deze schermopname ziet u de synchronisatie van de lokale historie in de view Synchroniseren. In de view kunt u de wijzigingen bekijken tussen de lokale resource en de laatste versie van de resource in de historie. Er is een aangepaste decorator beschikbaar voor het afbeelden van de datum/tijd van het item uit de lokale historie, en ook kunt u een aangepaste actie gebruiken om het bestand terug te zetten naar de vorige versie uit de lokale historie. Merk ook op dat de standaardpresentatie van de view Synchroniseren wordt gebruikt met probleemannotaties, layout voor gecomprimeerde mappen en navigatieknoppen.

De varianten voor de lokale historie definiëren

Allereerst definieert u een variant die de elementen uit de lokale historie vertegenwoordigt. Zo kunt u met de synchronisatie-API's de content opvragen van de lokale historie en vergelijken met de huidige content.

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;
}
}

Deze taak is eenvoudig, omdat de interface IFileState al toegang verschaft tot de content van het bestand uit de lokale historie (d.w.z. dat deze de interface IStorage al implementeert). Normaal gesproken moet u bij het maken van een variant opgeven hoe u de content wilt opvragen, wat het ID van de content is (waaraan de variant kan worden herkend) en hoe u de variant wilt noemen (een naam). De methode asBytes() is alleen verplicht als de variant tijdens sessies bewaard blijft.

Vervolgens wordt een variantcomparator gemaakt waarmee u de berekening SyncInfo kunt uitvoeren en lokale resources met de varianten kunt vergelijken. Deze taak is ook eenvoudig, omdat het bestaan van de lokale historie al impliceert dat de huidige content en de content uit de lokale historie van elkaar verschillen. Een lokale historie wordt immers alleen gemaakt als een bestand wordt gewijzigd.

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;
}
}

Omdat het bestaan van de lokale historie impliciet aanduidt dat deze van het lokale bestand verschilt, kan als resultaat van de vergelijking tussen de bestanden eenvoudig false worden gegeven. Bovendien werkt synchronisatie met de lokale historie alleen in twee richtingen, omdat we geen toegang hebben tot een basisresource en de methode voor het vergelijken van twee resourcevarianten dus niet gebruikt wordt.

Merk op dat de vergelijkingsmethode tijdens de synchronisatieberekening niet wordt aangeroepen als de variant niet bestaat (dus null is). De methode wordt alleen aangeroepen als beide elementen bestaan. In het voorbeeld wordt de methode aangeroepen voor zowel bestanden zonder lokale historie als alle mappen (waarvoor nooit een lokale historie wordt gemaakt). U kunt dit probleem aanpakken door de subklasse SyncInfo te definiëren, zodat de berekende synchronisatiestatus in deze gevallen kan worden gewijzigd.

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();
  }
}

De constructor is vervangen om altijd een basis van null op te kunnen geven (vanwege de vergelijking in twee richtingen) en de synchronisatieberekening is zodanig gewijzigd dat IN_SYNC als resultaat wordt gegeven als er geen bestand op afstand is (alleen de combinatie van een lokaal bestand en een bestand in de lokale historie is immers belangrijk).

De subscriber maken

De volgende stap is het maken van een subscriber waarmee u toegang tot de resourcevarianten in de lokale historie kunt krijgen. Omdat de lokale historie voor alle bestanden in het werkgebied kan worden bijgehouden, worden alle resources door een subscriber bewaakt en vormen alle projecten in het werkgebied de set roots. U hoeft de subscriber niet te vernieuwen, omdat de lokale historie alleen verandert zodra de inhoud van een lokaal bestand wordt gewijzigd. U kunt de status daarom bijwerken zodra een resourcedelta optreedt. De subscriber van de lokale historie heeft dan ook twee hoofdfuncties: de subklasse SyncInfo maken en het werkgebied onderzoeken.

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) {
        // alleen laatste status
        variant = new LocalHistoryVariant(states[0]);
      } 
    }
    SyncInfo info = new LocalHistorySyncInfo(resource, variant, comparator);
    info.init();
    return info;
  } catch (CoreException e) {
    throw TeamException.asTeamException(e);
  }
}

De subscriber geeft een nieuwe instance van SyncInfo met de laatste status van het bestand in de lokale historie als resultaat. De SyncInfo-instance wordt gemaakt met een variant uit de lokale historie voor het element op afstand. Voor projecten, mappen en bestanden zonder lokale historie hoeft u geen externe resourcevariant op te geven, zodat de resource als synchroon wordt beschouwd door de methode calculateKind van de LocalHistorySyncInfo-instance.

De resterende code in de subscriber van de lokale historie omvat de implementatie van de 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);
  }
}

Het interessante aan deze methode is dat niet-bestaande onderliggende items als resultaat worden geretourneerd voor verwijderde resources met een lokale historie. Zo kan de subscriber een instance van SyncInfo geven voor elementen die zich wel nog in de lokale historie maar niet meer in het werkgebied bevinden.

Een synchronisatiedeelnemer voor de lokale historie toevoegen

Tot dusver hebt u de klassen gemaakt waarmee u SyncInfo kunt opvragen van elementen in de lokale historie. Nu zijn de gebruikersinterface-elementen aan de beurt waarmee u een pagina aan de view Synchroniseren kunt toevoegen voor het afbeelden van de laatste status van alle elementen in de lokale historie. Het toevoegen hiervan is eenvoudig, omdat u al over een subscriber beschikt. Eerst wordt het extensiepunt voor een synchronisatiedeelnemer toegevoegd:

<extension
       point="org.eclipse.team.ui.synchronizeParticipants">
<participant
persistent="false"
icon="synced.png"
class="org.eclipse.team.synchronize.example.LocalHistoryParticipant"
name="Laatste uit lokale historie"
id="org.eclipse.team.synchronize.example"/>
</extension>

Nu moet LocalHistoryParticipant worden geïmplementeerd. SubscriberParticipant, met het standaardgedragspatroon voor het ophalen van SyncInfo uit de subscriber en het bijwerken van de synchronisatiestatus bij werkgebiedswijzigingen, wordt hier als subklasse aan toegevoegd. Bovendien wordt een actie toegevoegd voor het terugzetten van de resources in het werkgebied naar de laatste versies uit de lokale historie.

Eerst ziet u hoe u een aangepaste actie kunt toevoegen aan de deelnemer.

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("Laatste versie uit lokale historie terugzetten", configuration) { //$NON-NLS-1$
        protected SynchronizeModelOperation getSubscriberOperation(ISynchronizePageConfiguration configuration, IDiffElement[] elements) {
          return new RevertAllOperation(configuration, elements);
        }
      });
  }
}

In de bovenstaande code worden een specifieke SynchronizeModelAction en bewerking toegevoegd. U kunt hier profiteren van de mogelijkheid de taak op de achtergrond te laten uitvoeren en de bewerkte knooppunten als 'bezet' te markeren. De actie zorgt ervoor dat de laatste status uit de lokale historie wordt hersteld voor alle resources in het werkgebied. U kunt de actie toevoegen met behulp van een aanlevering voor de configuratie van de deelnemers. De configuratie wordt gebruikt om de eigenschappen te beschrijven voor het opzetten van de deelnemerspagina waarop de synchronisatie-gebruikersinterface wordt afgebeeld.

De configuratie wordt als volgt geïnitialiseerd door de deelnemer om de actiegroep aan het voorgrondmenu te kunnen toevoegen:

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());
}

Nu ziet u hoe u een aangepaste decoratie kunt maken. Op de laatste regel van de bovenstaande methode wordt de volgende decorator geregistreerd met de configuratie van de pagina.

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;
}
}

De decorator haalt de resource uit het modelelement in de synchronisatieview en voegt de contentidentificatie van de resourcevariant uit de lokale historie aan het tekstlabel in de view toe.

De laatste stap is het maken van een wizard voor het maken van de lokale-historiedeelnemer. Het perspectief 'Team synchroniseren' definieert een globale synchronisatie-actie waarmee een synchronisatie eenvoudig kan worden ingesteld. Synchronisaties kunnen ook via de werkbalk in de view Synchroniseren worden ingesteld. Om te beginnen, maakt u het extensiepunt synchronizeWizards:

<extension
point="org.eclipse.team.ui.synchronizeWizards">
<wizard
class="org.eclipse.team.synchronize.example.LocalHistorySynchronizeWizard"
icon="synced.png"
description="Synchroniseert alle resources in het werkgebied met de laatste versies van de resources in de lokale historie."
name="Laatste uit lokale-historiesynchronisatie"
id="ExampleSynchronizeSupport.wizard1"/>
</extension>

Zo wordt de wizard aan de lijst toegevoegd. In de methode finish() van de wizard wordt de deelnemer gemaakt en aan de synchronisatiemanager toegevoegd.

LocalHistoryPartipant participant = new LocalHistoryPartipant();
ISynchronizeManager manager = TeamUI.getSynchronizeManager();
manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant});
ISynchronizeView view = manager.showSynchronizeViewInActivePage();
view.display(participant);

Conclusie

In dit document hebt u een eenvoudig voorbeeld van de synchronisatie-API's gezien en hebt u kennis gemaakt met enkele synchronisatie-aspecten ter verduidelijking van het voorbeeld. Het maken van betrouwbare en accurate ondersteuning voor synchronisatie is geen gemakkelijke klus. Het beheer van de synchronisatiegegevens en de kennisgeving bij wijzigingen van de synchronisatiestatus vormen de moeilijkste taken. Zodra de implementatie van de subscriber is voltooid, is het opzetten van de gebruikersinterface eenvoudig (mits de interface van SubscriberParticipants toereikend is). Voor meer voorbeelden kunt u de plugin org.eclipse.team.example.filesystem raadplegen en de subklassen in het werkgebied van Subscriber en ISynchronizeParticipant bekijken.

In het volgende onderwerp komen enkele klassen en interfaces aan bod waarmee u een geheel nieuwe subscriber kunt maken en de synchronisatiestatus tussen sessies in de cache kunt plaatsen.