| /******************************************************************************* |
| * Copyright (c) 2010, 2011 Obeo. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Obeo - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.mylyn.docs.intent.client.synchronizer.synchronizer; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.emf.cdo.util.InvalidURIException; |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.Monitor; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.common.util.WrappedException; |
| import org.eclipse.emf.compare.Diff; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.SynchronizerRepositoryClient; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.api.contribution.ISynchronizerExtension; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.api.contribution.ISynchronizerExtensionRegistry; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.factory.SynchronizerStatusFactory; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.listeners.GeneratedElementListener; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.strategy.DefaultSynchronizerStrategy; |
| import org.eclipse.mylyn.docs.intent.client.synchronizer.strategy.SynchronizerStrategy; |
| import org.eclipse.mylyn.docs.intent.collab.common.logger.IIntentLogger.LogType; |
| import org.eclipse.mylyn.docs.intent.collab.common.logger.IntentLogger; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter; |
| import org.eclipse.mylyn.docs.intent.compare.utils.EMFCompareUtils; |
| import org.eclipse.mylyn.docs.intent.core.compiler.CompilationMessageType; |
| import org.eclipse.mylyn.docs.intent.core.compiler.CompilationStatus; |
| import org.eclipse.mylyn.docs.intent.core.compiler.InstructionTraceabilityEntry; |
| import org.eclipse.mylyn.docs.intent.core.compiler.TraceabilityIndex; |
| import org.eclipse.mylyn.docs.intent.core.compiler.TraceabilityIndexEntry; |
| import org.eclipse.mylyn.docs.intent.core.document.IntentGenericElement; |
| import org.eclipse.mylyn.docs.intent.core.modelingunit.ResourceDeclaration; |
| |
| /** |
| * In charge of comparing the compiled models of the repository with the compiled models generated at the |
| * location indicated by a Intent ResourceDeclaration. |
| * |
| * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a> |
| * @author <a href="mailto:william.piers@obeo.fr">William Piers</a> |
| */ |
| public class IntentSynchronizer { |
| |
| /** |
| * The Synchronizer strategy to use, defining several behaviors in case of conflict. |
| */ |
| private SynchronizerStrategy synchronizerStrategy; |
| |
| /** |
| * Listens generated elements. |
| */ |
| private GeneratedElementListener defaultSynchronizedElementListener; |
| |
| /** |
| * The repository client. |
| */ |
| private SynchronizerRepositoryClient repositoryClient; |
| |
| /** |
| * IntentSynchronizer constructor. |
| * |
| * @param synchronizerRepositoryClient |
| * the repositoryClient |
| */ |
| public IntentSynchronizer(SynchronizerRepositoryClient synchronizerRepositoryClient) { |
| this.repositoryClient = synchronizerRepositoryClient; |
| this.synchronizerStrategy = new DefaultSynchronizerStrategy(); |
| } |
| |
| /** |
| * Sets the Synchronizer strategy to use. |
| * |
| * @param strategy |
| * the Synchronizer strategy to use |
| */ |
| public void setSynchronizerStrategy(SynchronizerStrategy strategy) { |
| this.synchronizerStrategy = strategy; |
| } |
| |
| /** |
| * Sets the generatedElement listener, which will notify the Synchronizer if any generatedElement has |
| * changed. |
| * |
| * @param generatedElementListener |
| * the GeneratedElementListener |
| */ |
| public void setGeneratedElementListener(GeneratedElementListener generatedElementListener) { |
| this.defaultSynchronizedElementListener = generatedElementListener; |
| } |
| |
| /** |
| * Using the given traceability index, compares the compiled models of the repository with the compiled |
| * models generated at the location indicated by this index ; if the two models aren't similar, adds a |
| * status representing the differences to the returned list of status. |
| * |
| * @param adapter |
| * the repositoryAdapter to use for getting the repository content |
| * @param tracabilityIndex |
| * the Traceability index to use |
| * @param progressMonitor |
| * the progress Monitor indicating if this synchronization operation has been canceled |
| * @return a list containing status relatives to synchronization |
| * @throws InterruptedException |
| * if this operation was interrupted |
| */ |
| public Collection<? extends CompilationStatus> synchronize(RepositoryAdapter adapter, |
| TraceabilityIndex tracabilityIndex, Monitor progressMonitor) throws InterruptedException { |
| final List<CompilationStatus> statusList = new ArrayList<CompilationStatus>(); |
| final Collection<Resource> resourcesToUnload = Sets.newLinkedHashSet(); |
| if (defaultSynchronizedElementListener != null) { |
| defaultSynchronizedElementListener.clearElementToListen(); |
| } |
| Iterator<TraceabilityIndexEntry> indexEntryIterator = tracabilityIndex.getEntries().iterator(); |
| while (indexEntryIterator.hasNext()) { |
| this.stopIfCanceled(progressMonitor); |
| final TraceabilityIndexEntry indexEntry = indexEntryIterator.next(); |
| // First of all, we clear the old synchronization statuses |
| if (indexEntry.getResourceDeclaration() != null) { |
| clearSyncStatusesFromIndexEntry(indexEntry); |
| |
| // We do not synchronize abstract resources (i.e. resources with no associated URI) |
| if (indexEntry.getResourceDeclaration().getUri() != null) { |
| // We then generate the synchronization status for this entry |
| final Collection<? extends CompilationStatus> synchronizedStatus = synchronize(adapter, |
| indexEntry, resourcesToUnload, progressMonitor); |
| statusList.addAll(synchronizedStatus); |
| } |
| } |
| } |
| |
| // Unload all external resources |
| for (Resource resource : resourcesToUnload) { |
| try { |
| resource.unload(); |
| // CHECKSTYLE:OFF |
| } catch (Exception e) { |
| // CHECKSTYLE:ON |
| IntentLogger.getInstance().logError(e); |
| } |
| } |
| return statusList; |
| } |
| |
| /** |
| * Clears the synchronization statues associated to the given indexEntry (i.e in the ResourceDeclaration |
| * and the instructions that describes the generated content). |
| * |
| * @param indexEntry |
| * the index Entry containing the instructions to clear |
| */ |
| private void clearSyncStatusesFromIndexEntry(TraceabilityIndexEntry indexEntry) { |
| |
| // We first remove the synchronization statutes associated to the resource Declaration |
| Iterator<CompilationStatus> statusIterator = indexEntry.getResourceDeclaration() |
| .getCompilationStatus().iterator(); |
| |
| while (statusIterator.hasNext()) { |
| CompilationStatus status = statusIterator.next(); |
| if (isSyncStatus(status)) { |
| statusIterator.remove(); |
| } |
| } |
| // Then, for each mapped element |
| for (EObject containedElement : indexEntry.getContainedElementToInstructions().keySet()) { |
| |
| // We must remove the synchronization statuses from the instruction that generated this |
| // element |
| EList<InstructionTraceabilityEntry> instructionEntries = indexEntry |
| .getContainedElementToInstructions().get(containedElement); |
| if (instructionEntries != null) { |
| for (InstructionTraceabilityEntry instructionTraceabilityEntry : instructionEntries) { |
| IntentGenericElement instruction = instructionTraceabilityEntry.getInstruction(); |
| if (instruction != null) { |
| clearSyncStatusesFromInstruction(instruction); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clears the synchronization statues associated to the given instruction. |
| * |
| * @param instruction |
| * the instruction to clear |
| */ |
| private void clearSyncStatusesFromInstruction(IntentGenericElement instruction) { |
| EList<CompilationStatus> compilationStatus = instruction.getCompilationStatus(); |
| if (compilationStatus != null) { |
| Iterator<CompilationStatus> iterator = compilationStatus.iterator(); |
| while (iterator.hasNext()) { |
| CompilationStatus status = iterator.next(); |
| if (isSyncStatus(status)) { |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns true if the given status is related to a synchronization issue. |
| * |
| * @param status |
| * the status to test |
| * @return true if the given status is related to a synchronization issue |
| */ |
| private boolean isSyncStatus(CompilationStatus status) { |
| CompilationMessageType type = status.getType(); |
| return type.equals(CompilationMessageType.SYNCHRONIZER_WARNING) |
| || type.equals(CompilationMessageType.SYNCHRONIZER_INFO); |
| } |
| |
| /** |
| * Using the given TraceabilitIndexEntry, compares the model located at the indicated path with the model |
| * located at the path indicated by the resource declaration. |
| * |
| * @param adapter |
| * the repositoryAdapter to use for getting the repository content |
| * @param indexEntry |
| * the indexEntry to use for obtaining synchronization informations |
| * @param resourcesToUnload |
| * the resources that should be unloaded after synchronization |
| * @param progressMonitor |
| * the progress Monitor indicating if this synchronization operation has been canceled |
| * @return a list of status relatives to synchronization of the model described in the given indexEntry |
| * @throws InterruptedException |
| * if this operation was interrupted |
| */ |
| private Collection<? extends CompilationStatus> synchronize(final RepositoryAdapter adapter, |
| final TraceabilityIndexEntry indexEntry, Collection<Resource> resourcesToUnload, |
| Monitor progressMonitor) throws InterruptedException { |
| List<CompilationStatus> statusList = new ArrayList<CompilationStatus>(); |
| boolean continueSynchronization = true; |
| |
| // Step 1 : getting the repository resource |
| stopIfCanceled(progressMonitor); |
| Resource internalResource = getInternalResource(adapter, indexEntry); |
| |
| stopIfCanceled(progressMonitor); |
| // Step 2 : getting the generated resource |
| Resource externalResource = getExternalResource(adapter, indexEntry); |
| |
| stopIfCanceled(progressMonitor); |
| // Step 3 : if one of the resource is null, |
| // we use the strategy to handle these cases |
| if (internalResource == null) { |
| final List<Resource> result = new ArrayList<Resource>(); |
| final Resource finalExternalResource = externalResource; |
| result.add(synchronizerStrategy.handleNullInternalResource(indexEntry.getGeneratedResourcePath(), |
| finalExternalResource)); |
| if (!result.isEmpty()) { |
| internalResource = result.get(0); |
| } |
| |
| // TODO : we can create here a status if the internal Resource has not been created |
| continueSynchronization = internalResource != null; |
| } |
| if (externalResource == null) { |
| final List<Resource> result = new ArrayList<Resource>(); |
| final Resource finalInternalResource = internalResource; |
| Resource handleNullExternalResource = synchronizerStrategy.handleNullExternalResource(indexEntry |
| .getResourceDeclaration(), finalInternalResource, indexEntry.getResourceDeclaration() |
| .getUri()); |
| if (handleNullExternalResource != null) { |
| result.add(handleNullExternalResource); |
| } |
| if (!result.isEmpty()) { |
| externalResource = result.get(0); |
| } else { |
| Collection<? extends CompilationStatus> statusForNullExternalresource = synchronizerStrategy |
| .getStatusForNullExternalResource(indexEntry.getResourceDeclaration(), |
| indexEntry.getGeneratedResourcePath()); |
| statusList.addAll(statusForNullExternalresource); |
| updateSynchronizedElementsListeners(getResourceDeclarationURI(indexEntry |
| .getResourceDeclaration())); |
| } |
| |
| // TODO : we can create here a status if the external Resource has not been created |
| continueSynchronization = externalResource != null; |
| } |
| |
| stopIfCanceled(progressMonitor); |
| // If no resource was null or if the strategy authorizes the operation to continue |
| if (continueSynchronization) { |
| |
| if (externalResource.getContents().isEmpty() && !internalResource.getContents().isEmpty()) { |
| Collection<? extends CompilationStatus> statusForEmptyExternalresource = synchronizerStrategy |
| .getStatusForEmptyExternalResource(indexEntry.getResourceDeclaration(), |
| indexEntry.getGeneratedResourcePath()); |
| statusList.addAll(statusForEmptyExternalresource); |
| updateSynchronizedElementsListeners(getResourceDeclarationURI(indexEntry |
| .getResourceDeclaration())); |
| } else if (internalResource.getContents().isEmpty() && !externalResource.getContents().isEmpty()) { |
| Collection<? extends CompilationStatus> statusForEmptyInternalResource = synchronizerStrategy |
| .getStatusForEmptyInternalResource(indexEntry.getResourceDeclaration(), |
| indexEntry.getGeneratedResourcePath()); |
| statusList.addAll(statusForEmptyInternalResource); |
| updateSynchronizedElementsListeners(getResourceDeclarationURI(indexEntry |
| .getResourceDeclaration())); |
| } else { |
| |
| // Step 4 : comparing those two resources |
| Resource leftResource = synchronizerStrategy.getLeftResource(internalResource, |
| externalResource); |
| Resource rightResource = synchronizerStrategy.getRightResource(internalResource, |
| externalResource); |
| try { |
| List<Diff> differences = null; |
| stopIfCanceled(progressMonitor); |
| differences = compareResource(indexEntry, leftResource, rightResource); |
| |
| stopIfCanceled(progressMonitor); |
| // Step 5 : creating status from the Diff |
| statusList = createSynchronizerSatusListFromComparison(indexEntry, differences, |
| progressMonitor); |
| resourcesToUnload.add(rightResource); |
| // CHECKSTYLE:OFF |
| } catch (Exception e) { |
| // CHECKSTYLE:ON |
| // Unloading the external resource if issue was encountered |
| try { |
| externalResource.unload(); |
| // CHECKSTYLE:OFF |
| } catch (Exception e2) { |
| // CHECKSTYLE:ON |
| IntentLogger.getInstance().logError(e2); |
| } |
| } |
| |
| // Step 7 : we ask the generated element listener to listen to the external Resource |
| updateSynchronizedElementsListeners(externalResource.getURI()); |
| } |
| } else { |
| stopIfCanceled(progressMonitor); |
| // TODO we can imagine creating a status, unless it's the responsability of the Strategy |
| |
| } |
| |
| return statusList; |
| } |
| |
| /** |
| * Notifies the listeners in charge of detecting any changes made outside of repository that this |
| * synchronizer wants to listen the resource located at the given uri. |
| * |
| * @param uri |
| * the uri of the resource the synchronizer wants to listen |
| */ |
| public void updateSynchronizedElementsListeners(URI uri) { |
| |
| boolean foundSpecificSynchronizer = false; |
| |
| // Step 1 : searching through all contributed SynchronizerExtensions |
| // for an Extension matching the scheme of the given uri |
| for (ISynchronizerExtension synchronizerExtension : ISynchronizerExtensionRegistry |
| .getSynchronizerExtensions(uri)) { |
| if (synchronizerExtension != null) { |
| synchronizerExtension.addListenedElements(repositoryClient, Sets.newHashSet(uri)); |
| foundSpecificSynchronizer = true; |
| } |
| } |
| |
| // Step 2 : if no synchronizer extensions is define for the given URI, then we notify the generated |
| // elements listener |
| if (!foundSpecificSynchronizer && this.defaultSynchronizedElementListener != null) { |
| this.defaultSynchronizedElementListener.addElementToListen(uri.trimFragment()); |
| } |
| |
| } |
| |
| /** |
| * Ensure the current synchronization operation (represented by the given monitor) hasn't been canceled ; |
| * if so, throws an InterruptedException. |
| * |
| * @param progressMonitor |
| * the progressMonitor to use for determining if the operation has been canceled |
| * @throws InterruptedException |
| * if the operation has been canceled |
| */ |
| private void stopIfCanceled(Monitor progressMonitor) throws InterruptedException { |
| if (progressMonitor.isCanceled()) { |
| throw new InterruptedException(); |
| } |
| |
| } |
| |
| /** |
| * Creates the synchronization statuses corresponding to the given list of {@link Diff}. |
| * |
| * @param indexEntry |
| * the indexEntry indicating the compared resources |
| * @param differences |
| * the list of {@link Diff} between the compared resources |
| * @param progressMonitor |
| * the progress Monitor indicating if this synchronization operation has been canceled |
| * @return a list containing the synchronization statuses corresponding to the given list of {@link Diff} |
| * @throws InterruptedException |
| * if the operation has been canceled |
| */ |
| private List<CompilationStatus> createSynchronizerSatusListFromComparison( |
| TraceabilityIndexEntry indexEntry, List<Diff> differences, Monitor progressMonitor) |
| throws InterruptedException { |
| Map<IntentGenericElement, Collection<CompilationStatus>> elementToSyncStatus = Maps |
| .newLinkedHashMap(); |
| List<CompilationStatus> statusList = new ArrayList<CompilationStatus>(); |
| |
| for (Diff difference : differences) { |
| stopIfCanceled(progressMonitor); |
| // For each synchronization status relative to the consider Diff |
| for (CompilationStatus newStatus : SynchronizerStatusFactory.createStatusFromDiff(indexEntry, |
| difference)) { |
| stopIfCanceled(progressMonitor); |
| |
| if (elementToSyncStatus.get(newStatus.getTarget()) == null) { |
| elementToSyncStatus.put(newStatus.getTarget(), Lists.<CompilationStatus> newArrayList()); |
| } |
| elementToSyncStatus.get(newStatus.getTarget()).add(newStatus); |
| statusList.add(newStatus); |
| } |
| } |
| |
| return statusList; |
| } |
| |
| /** |
| * Return the resource containing the compiled model currently inspected (<b>internal</b> resource of the |
| * repository). |
| * |
| * @param adapter |
| * the repositoryAdapter to use for getting the repository content |
| * @param indexEntry |
| * the indexEntry indicating the location of the compiled resource |
| * @return the resource containing the compiled model currently inspected (<b>internal</b> resource of the |
| * repository) |
| */ |
| private Resource getInternalResource(RepositoryAdapter adapter, TraceabilityIndexEntry indexEntry) { |
| try { |
| return adapter.getResource(indexEntry.getGeneratedResourcePath()); |
| // CHECKSTYLE:OFF |
| } catch (Exception e) { |
| // CHECKSTYLE:ON |
| return null; |
| } |
| } |
| |
| /** |
| * Returns the resource containing the compiled model currently inspected (<b>external</b> resource of the |
| * repository : can be in a workspace, on internet...). |
| * |
| * @param adapter |
| * the current {@link RepositoryAdapter} |
| * @param indexEntry |
| * the indexEntry indicating the location of the compiled resource |
| * @return the resource containing the compiled model currently inspected (<b>external</b> resource of the |
| * repository) - can be null if the resource doesn't exist. |
| */ |
| private Resource getExternalResource(RepositoryAdapter adapter, TraceabilityIndexEntry indexEntry) { |
| ResourceSet resourceSet = adapter.getResourceSet(); |
| Resource resource = null; |
| if (indexEntry.getResourceDeclaration() != null) { |
| URI externalURI = getResourceDeclarationURI(indexEntry.getResourceDeclaration()); |
| |
| try { |
| resource = resourceSet.getResource(externalURI, true); |
| } catch (WrappedException e) { |
| resource = null; |
| } catch (InvalidURIException e) { |
| resource = null; |
| } catch (IllegalArgumentException e) { |
| resource = null; |
| } |
| } |
| return resource; |
| } |
| |
| /** |
| * Compares the roots of the given resources and return a list of differences. |
| * |
| * @param indexEntry |
| * the index entry declaring the left & right resources |
| * @param leftResource |
| * the <i>"left"</i> resource of the two resources to get compared. |
| * @param rightResource |
| * the <i>"right"</i> resource of the two resources to get compared. |
| * @return a list of Diff corresponding to all the differences between the left resources and the right |
| * resource |
| * @throws InterruptedException |
| * if the comparison is interrupted |
| */ |
| private List<Diff> compareResource(TraceabilityIndexEntry indexEntry, Resource leftResource, |
| Resource rightResource) throws InterruptedException { |
| List<Diff> differences = Lists.newArrayList(); |
| try { |
| |
| URI workingCopyResourceURI = URI.createURI(indexEntry.getResourceDeclaration().getUri() |
| .toString()); |
| Notifier leftTarget = leftResource; |
| Notifier rightTarget = rightResource; |
| // If we are synchronizing the whole resource |
| if (workingCopyResourceURI.hasFragment()) { |
| leftTarget = leftResource.getContents().iterator().next(); |
| rightTarget = rightResource.getEObject(workingCopyResourceURI.fragment()); |
| } |
| if (leftTarget != null && rightTarget != null) { |
| differences.addAll(EMFCompareUtils.compare(leftTarget, rightTarget).getDifferences()); |
| } else { |
| IntentLogger.getInstance().log(LogType.ERROR, |
| "Could not synchronize resource " + workingCopyResourceURI + ": resource was null"); |
| } |
| // CHECKSTYLE:OFF |
| } catch (Exception e) { |
| // CHECKSTYLE :ON |
| IntentLogger.getInstance().logError(e); |
| } |
| return differences; |
| |
| } |
| |
| /** |
| * Disposes elements. |
| */ |
| public void dispose() { |
| defaultSynchronizedElementListener.dispose(); |
| } |
| |
| private static URI getResourceDeclarationURI(ResourceDeclaration resourceDeclaration) { |
| return URI.createURI(resourceDeclaration.getUri().toString().replace("\"", "")); |
| } |
| } |