Il modo migliore per comprendere le API di sincronizzazione è creare un esempio semplice ma concreto. In questo esempio, verrà creata una pagina nella vista Sincronizzazione in cui viene visualizzato l'ultimo stato della cronologia locale per tutti i file dello spazio di lavoro. La sincronizzazione della cronologia locale viene aggiornata automaticamente quando vengono apportate modifiche allo spazio di lavoro e un editor di confronto può essere aperto per esaminare ed unire tali modifiche. Verranno inoltre aggiunti un decoratore personalizzato per mostrare l'ultima data/ora dell'elemento della cronologia locale e un'azione per riportare i file dello spazio di lavoro all'ultimo stato delle cronologia locale salvato. Si tratta di un esempio perfetto, in quanto è già disponibile un archivio di varianti di risorse e non è necessario gestirlo.
Per la parte restante di questa sezione, viene utilizzato un esempio concreto. La maggior parte, ma non tutto il codice di origine, verrà incluso in questa pagina. Il codice di origine completo è disponibile nel pacchetto della cronologia locale del plugin org.eclipse.team.examples.filesystem. È possibile estrarre il progetto dal repository CVS e utilizzarlo come riferimento durante la lettura di questa esercitazione. Dichiarazione di responsabilità: il codice di origine nel plug-ins di esempio può cambiare con il
tempo. Per visualizzare una copia che corrisponda al codice utilizzato in questo esempio, è possibile estrarre il progetto mediante il tag della versione 3.0
(con maggiore probabilità R3_0) o un tag con la data del 28 giugno 2004.
In questo pannello viene visualizzata la sincronizzazione della cronologia locale nella vista Sincronizzazione. Con questa sincronizzazione, è possibile esaminare le modifiche tra la risorsa locale e l'ultimo stato della cronologia. La sincronizzazione dispone di un decoratore personalizzato per visualizzare la data/ora associate alla cronologia locale e di un'azione personalizzata per riportare il file al contenuto della cronologia locale. Viene utilizzata la presentazione della vista Sincronizzazione standard, che fornisce le note sui problemi, il layout della cartella compressa e i pulsanti di spostamento.
Il primo passo è definire una variante che rappresenti gli elementi della cronologia locale. Ciò consente alle API di sincronizzazione di accedere al contenuto della cronologia locale, in modo da paragonare tale contenuto con il contenuto corrente, visualizzato dall'utente.
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;
}
}
Poiché l'interfaccia IFileState già fornisce l'accesso al contenuto del file della cronologia locale (ad esempio implementa l'interfaccia IStorage), ciò è molto semplice.
In genere, quando si crea una variante, è necessario fornire un modo per accedere al contenuto, un identificativo del contenuto visualizzato dall'utente per identificare questa variante e un nome. Il metodo asBytes() è richiesto solo se la variante persiste tra le sessioni.
Successivamente, viene creato un comparatore di varianti che consente il calcolo SyncInfo, per confrontare le risorse locali con le relative varianti. Anche questa operazione è semplice, perché l'esistenza di uno stato della cronologia locale implica che il contenuto dello stato della cronologia locale differisca dal contenuto corrente del file. Ciò perché la specifica relativa alla cronologia locale indica che non verrà creato uno stato di cronologia locale se il file non viene modificato.
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;
}
}
Poiché l'esistenza dello stato della cronologia locale implica una differenza dal file locale, quando si confronta il file con lo stato della cronologia locale, viene riportato il valore false. Inoltre, la sincronizzazione con la cronologia locale è solo a due vie, perché non si dispone dell'accesso a una risorsa di base, per cui il metodo per il confronto di due varianti di risorse non viene utilizzato.
Il calcolo della sincronizzazione non richiama il metodo di confronto del comparatore, se la variante non esiste (ad esempio è nulla). Tale metodo viene richiamato solo se esistono entrambi gli elementi. Nell'esempio riportato, ciò si verifica per i file che non dispongono di una cronologia locale e per tutte le cartelle (che non dispongono mai di una cronologia locale). Per questo motivo, è necessario definire la classe secondaria di SyncInfo, per modificare lo stato di sincronizzazione calcolato per questi casi.
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();
}
}
Il costruttore è stato sostituito, per fornire sempre una base che sia null (poiché viene utilizzato solo il confronto a due fasi) ed è stato modificato il calcolo di sincronizzazione, in modo da ottenere il valore IN_SYNC, se non esiste alcun file remoto (poiché vengono considerati solo i casi in cui esiste un file locale e uno stato file nella cronologia locale).
Verrà ora creato un sottoscrittore che fornisce l'accesso alle varianti delle risorse nella cronologia locale. Poiché la cronologia locale può essere salvata per qualsiasi file dello spazio di lavoro, il sottoscrittore della cronologia locale supervisiona tutte le risorse e l'insieme di elementi principali rappresenta tutti i progetti dello spazio di lavoro. Inoltre, non è necessario fornire la funzione per aggiornare il sottoscrittore, poiché la cronologia locale viene modificata solo quando viene modificato il contenuto di un file locale. È possibile quindi aggiornare lo stato ogni volta che si verifica il delta di una risorsa. Nel sottoscrittore della cronologia locale restano solo due metodi interessanti: il richiamo di SyncInfo e il passaggio dello spazio di lavoro.
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);
}
}
Il sottoscrittore restituisce una nuova istanza SyncInfo che contiene l'ultimo stato del file nella cronologia locale. L'istanza SyncInfo viene creata con una variante della cronologia locale per l'elemento remoto. Per i progetti, le cartelle ed i file senza una cronologia locale, non viene fornita alcuna variante della risorsa remota, il che darà come risultato una risorsa considerata in-sync per il metodo calculateKind in LocalHistorySyncInfo.
Il codice restante nel sottoscrittore della cronologia locale rappresenta l'implementazione del metodo 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);
}
}
Il dettaglio interessante di questo metodo sta nel fatto che restituisce elementi secondari non esistenti, se una risorsa eliminata dispone di una cronologia locale. Ciò consente al sottoscrittore di restituire un'istanza SyncInfo per gli elementi presenti solo nella cronologia locale e che non esistono più nello spazio di lavoro.
Le classi che forniscono l'accesso all'istanza SyncInfo per gli elementi della cronologia locale sono state create. Quindi, verranno creati gli elementi dell'UI che consentono di disporre di una pagina nella vista Sincronizzazione per visualizzare l'ultimo stato della cronologia per tutti gli elementi della cronologia locale. Dal momento che si dispone di un sottoscrittore, è semplice aggiungere il sottoscrittore alla vista Sincronizzazione. Innanzitutto viene aggiunto un punto di estensione di un partecipante di sincronizzazione:
<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>
È necessario quindi implementare LocalHistoryParticipant. Verrà stabilita una sottoclasse per SubscriberParticipant, che fornirà il comportamento predefinito per richiamare l'istanza SyncInfo dal sottoscrittore e aggiornare gli stati sync quando si verificano modifiche allo spazio di lavoro. Inoltre, verrà aggiunta un'azione per riportare le risorse dello spazio di lavoro all'ultimo stato della cronologia locale.
Innanzitutto, verrà esaminato come un'azione personalizzata viene aggiunta al partecipante.
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);
}
});
}
}
Qui vengono aggiunti un SynchronizeMoidelAction specifico ed una relativa operazione. Questa attività prevede la possibilità di eseguire l'operazione sullo sfondo e di visualizzare lo stato busy per i nodi utilizzati. Questa azione riporta tutte le risorse dello spazio di lavoro all'ultimo stato della cronologia locale. Questa azione viene aggiunta aggiungendo un contributo dell'azione alla configurazione dei partecipanti. La configurazione viene utilizzata per descrivere le proprietà necessarie per generare la pagina dei partecipanti, in cui viene visualizzata l'UI di sincronizzazione reale.
Il partecipante inizializza la configurazione come di seguito riportato, per aggiungere il gruppo di azioni della cronologia locale al menu di scelta rapida:
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());
}
Viene ora descritto come fornire una decorazione personalizzata. L'ultima riga del metodo precedente registra il seguente decoratore con la configurazione della 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;
}
}
Il decoratore estrae la risorsa dall'elemento modello visualizzato nella vista sincronizzazione e aggiunge l'identificativo del contenuto della variante di risorsa della cronologia locale all'etichetta di testo visualizzata nella vista.
L'ultima parte descrive come fornire una procedura guidata per creare il partecipante della cronologia locale. La prospettiva Sincronizzazione team definisce un'azione di sincronizzazione globale che consente agli utenti di creare rapidamente una sincronizzazione. La funzione per creare sincronizzazioni, inoltre, è disponibile nella barra degli strumenti della vista Sincronizzazione. Per avviare l'attività, creare un punto di estensione synchronizeWizards:
<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>
La procedura guidata viene aggiunta all'elenco e nel metodo finish() delle procedure guidate viene creato un partecipante e aggiunto al gestore sincronizzazione.
LocalHistoryPartipant participant = new LocalHistoryPartipant();
ISynchronizeManager manager = TeamUI.getSynchronizeManager();
manager.addSynchronizeParticipants(new ISynchronizeParticipant[] {participant});
ISynchronizeView view = manager.showSynchronizeViewInActivePage();
view.display(participant);
È stato illustrato un semplice esempio di utilizzo delle API di sincronizzazione, omettendo alcuni dettagli per rendere l'esempio più semplice.
La generazione di un supporto di sincronizzazione efficace ed accurato non è da sottovalutare, essendo la parte più complessa quella relativa alla gestione delle informazioni sulla sincronizzazione e alla notifica delle modifiche dello stato di sincronizzazione. L'interfaccia utente, se l'interfaccia associata a SubscriberParticipants è adeguata, è la parte più semplice, una volta che l'implementazione del sottoscrittore risulta completa. Per altri esempi, fare riferimento al plugin org.eclipse.team.example.filesystem ed esaminare le sottoclassi
dello spazio di lavoro di Subscriber e ISynchronizeParticipant.
La sezione successiva descrive alcune classi ed interfacce che consentono di generare un sottoscrittore da zero, inclusa la memorizzazione nella cache degli stati di sincronizzazione tra le sessioni del workbench.