| /******************************************************************************* |
| * 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.collab.ide.adapters; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.ConcurrentModificationException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.command.CommandStack; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.URIConverter; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.emf.ecore.xmi.XMLResource; |
| import org.eclipse.emf.ecore.xmi.impl.XMLParserPoolImpl; |
| import org.eclipse.emf.transaction.RecordingCommand; |
| import org.eclipse.emf.transaction.TransactionalEditingDomain; |
| import org.eclipse.emf.transaction.impl.InternalTransactionalEditingDomain; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.IntentCommand; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.ReadOnlyException; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryStructurer; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.adapters.SaveException; |
| import org.eclipse.mylyn.docs.intent.collab.handlers.notification.Notificator; |
| import org.eclipse.mylyn.docs.intent.collab.ide.notification.WorkspaceTypeListener; |
| import org.eclipse.mylyn.docs.intent.collab.ide.repository.WorkspaceRepository; |
| import org.eclipse.mylyn.docs.intent.collab.ide.repository.WorkspaceSession; |
| import org.eclipse.mylyn.docs.intent.collab.repository.Repository; |
| import org.eclipse.mylyn.docs.intent.collab.repository.RepositoryConnectionException; |
| |
| /** |
| * Adapter that allows the RepositoryObjectHandler to work with an Eclipse Workspace. |
| * |
| * @author <a href="mailto:alex.lagarde@obeo.fr">Alex Lagarde</a> |
| * @author <a href="mailto:william.piers@obeo.fr">William Piers</a> |
| */ |
| public class WorkspaceAdapter implements RepositoryAdapter { |
| |
| /** |
| * Represents the time to wait before asking the Workspace Session if it's locked. |
| */ |
| private static final long TIME_TO_WAIT_BEFORE_CHECKING_SESSIONDELTA = 5; |
| |
| /** |
| * Time to wait for the editing domain availability (if exceded, then we will not execute the command and |
| * throw an exception). |
| */ |
| private static final long TIMEOUT = 15000; |
| |
| /** |
| * The save options that have to be used for saving resources of this repository. |
| */ |
| private static Map<String, Object> saveOptions; |
| |
| /** |
| * The load options that have to be used for loading this repository. |
| */ |
| private static Map<String, Object> loadOptions; |
| |
| /** |
| * The Workspace repository associated to this adapter. |
| */ |
| private WorkspaceRepository repository; |
| |
| /** |
| * Indicates if the current opened context is ReadOnly (false if Read/Write). |
| */ |
| private boolean isReadOnlyContext; |
| |
| /** |
| * Mapping between notificator and listeners. |
| */ |
| private Map<Notificator, Set<WorkspaceTypeListener>> notificatorToListener; |
| |
| /** |
| * The {@link RepositoryStructurer} used to structured the IntentDocument. |
| */ |
| private RepositoryStructurer documentStructurer; |
| |
| /** |
| * Indicates if this adapter should send or not warning to the WorkspaceSession before saving a resource |
| * (default value : true). |
| */ |
| private boolean sendSessionWarningBeforeSaving; |
| |
| /** |
| * A list of path that should not cause warning sending to the WorkspaceSession before saving a resource |
| * conform to the given path. |
| * <p> |
| * For example, if this list contain '/FOLDER/SUBFOLDER1/', any resource located in this folder or |
| * sub-folders should be ignored. It can also contains path relative to a single resource, like |
| * '/FOLDER/SUBFOLDER1/MySingleResource'. |
| * </p> |
| */ |
| private List<String> resourcesToIgnorePaths; |
| |
| /** |
| * A {@link Predicate} that returns true if the considered resource must not be unloaded when undoing |
| * changes, false otherwise. |
| */ |
| private Predicate<Resource> unloadableResourcePredicate = new Predicate<Resource>() { |
| |
| public boolean apply(Resource input) { |
| // Default implementation considers that all resources should be unloaded when undoing changes |
| return false; |
| } |
| }; |
| |
| /** |
| * WorkspaceAdapterconstructor. |
| * |
| * @param repository |
| * the Workspace repository associated to this adapter |
| */ |
| public WorkspaceAdapter(WorkspaceRepository repository) { |
| this.repository = repository; |
| this.isReadOnlyContext = false; |
| this.notificatorToListener = new HashMap<Notificator, Set<WorkspaceTypeListener>>(); |
| this.resourcesToIgnorePaths = new ArrayList<String>(); |
| } |
| |
| /** |
| * Returns the save options that have to be used for saving resources. |
| * |
| * @return the save options that have to be used for saving resources |
| */ |
| public static Map<String, Object> getSaveOptions() { |
| if (saveOptions == null) { |
| saveOptions = new HashMap<String, Object>(); |
| // We do not format the document when saving (less human-readable but faster to save and to load) |
| saveOptions.put(XMLResource.OPTION_FORMATTED, false); |
| } |
| return saveOptions; |
| } |
| |
| /** |
| * Returns the load options that have to be used for loading resources. |
| * |
| * @return the load options that have to be used for loading resources |
| */ |
| public static Map<String, Object> getLoadOptions() { |
| if (loadOptions == null) { |
| loadOptions = new HashMap<String, Object>(); |
| // We want the resource to be saved only if changes have been detected. |
| // In order to make the system as scalable as possible, |
| // We use a fileBuffer instead of a memory buffer |
| loadOptions.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true); |
| // Parser Pool for tweaking performances |
| loadOptions.put(XMLResource.OPTION_USE_PARSER_POOL, new XMLParserPoolImpl()); |
| } |
| return loadOptions; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#openSaveContext() |
| */ |
| public Object openSaveContext() { |
| // Nothing to do here except setting the isReadOnluContext value to false |
| isReadOnlyContext = false; |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#openReadOnlyContext() |
| */ |
| public Object openReadOnlyContext() { |
| // Nothing to do here except setting the isReadOnluContext value to true |
| isReadOnlyContext = true; |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#save() |
| */ |
| public void save() throws ReadOnlyException, SaveException { |
| if (isReadOnlyContext) { |
| throw new ReadOnlyException( |
| "Cannot save with a read-only context. The context should have been started with the 'openSaveContext' method."); |
| } |
| |
| // Step 1: we use the documentStructurer to structure the resource set |
| if (documentStructurer != null) { |
| documentStructurer.structure(WorkspaceAdapter.this); |
| } |
| final Collection<Resource> resources = Lists.newArrayList(this.repository.getResourceSet() |
| .getResources()); |
| SaveException saveException = null; |
| try { |
| for (Resource resource : resources) { |
| if (resource != null && isRepositoryResource(resource.getURI())) { |
| try { |
| if (!removeDanglingElements(resource) && hasDifferentSerialization(resource)) { |
| try { |
| // We make sure the session isn't still reacting to previous saves |
| while (((WorkspaceSession)this.repository.getOrCreateSession()) |
| .isProcessingDelta()) { |
| Thread.sleep(TIME_TO_WAIT_BEFORE_CHECKING_SESSIONDELTA); |
| } |
| |
| // Step 2: we send a warning to the WorkspaceSession if necessary |
| treatSessionWarning(resource); |
| |
| // Step 3: save the resource |
| |
| if (resource.getContents().isEmpty()) { |
| // if the resource is empty, we delete it |
| resource.delete(getSaveOptions()); |
| } else { |
| resource.save(getSaveOptions()); |
| } |
| } catch (IOException e) { |
| removeDanglingElements(resource); |
| } catch (RepositoryConnectionException e) { |
| saveException = new SaveException(e.getMessage()); |
| } |
| } |
| } catch (RepositoryConnectionException e) { |
| saveException = new SaveException(e.getMessage()); |
| } catch (IOException e) { |
| saveException = new SaveException(e.getMessage()); |
| } catch (InterruptedException e) { |
| throw new SaveException(e.getMessage()); |
| } catch (UnsupportedOperationException e) { |
| // Silently removing resource : it is not saveable |
| this.repository.getResourceSet().getResources().remove(resource); |
| } catch (IllegalStateException e) { |
| e.printStackTrace(); |
| } |
| |
| } else { |
| // If repository generated elements reference external content, then we should not save |
| // them: the save method purpose is to commit the modification made on the repository |
| this.repository.getResourceSet().getResources().remove(resource); |
| } |
| } |
| } catch (ConcurrentModificationException cme) { |
| // If there were a concurrent modification, we simply retry |
| // FIXME : can we make a better choice ? The causes of this exception don't seem obvious |
| save(); |
| } |
| if (saveException != null) { |
| throw saveException; |
| } |
| |
| } |
| |
| /** |
| * Returns true if the resource will get a different serialization than the one on the disk. |
| * |
| * @param resourcetoSave |
| * the resource to serialize |
| * @return true if the resource will get a different serialization than the one on the disk. |
| * @throws IOException |
| * on error while saving. |
| */ |
| public boolean hasDifferentSerialization(final Resource resourcetoSave) throws IOException { |
| // CHECKSTYLE:OFF : code coming from |
| // ResourceImpl.saveOnlyIfChangedWithFileBuffer |
| resourcetoSave.eSetDeliver(false); |
| final File temporaryFile = File.createTempFile("ResourceSaveHelper", null); |
| boolean equal = true; |
| try { |
| final URI temporaryFileURI = URI.createFileURI(temporaryFile.getPath()); |
| |
| final URIConverter uriConverter = resourcetoSave.getResourceSet() == null ? new ResourceSetImpl() |
| .getURIConverter() : resourcetoSave.getResourceSet().getURIConverter(); |
| final OutputStream temporaryFileOutputStream = uriConverter.createOutputStream(temporaryFileURI); |
| try { |
| resourcetoSave.save(temporaryFileOutputStream, getSaveOptions()); |
| } finally { |
| temporaryFileOutputStream.close(); |
| } |
| |
| InputStream oldContents = null; |
| try { |
| oldContents = uriConverter.createInputStream(resourcetoSave.getURI()); |
| } catch (final IOException exception) { |
| equal = false; |
| } |
| final byte[] newContentBuffer = new byte[4000]; |
| if (oldContents != null) { |
| try { |
| final InputStream newContents = uriConverter.createInputStream(temporaryFileURI); |
| try { |
| final byte[] oldContentBuffer = new byte[4000]; |
| LOOP: for (int oldLength = oldContents.read(oldContentBuffer), newLength = newContents |
| .read(newContentBuffer); (equal = oldLength == newLength) && oldLength > 0; oldLength = oldContents |
| .read(oldContentBuffer), newLength = newContents.read(newContentBuffer)) { |
| for (int i = 0; i < oldLength; ++i) { |
| if (oldContentBuffer[i] != newContentBuffer[i]) { |
| equal = false; |
| break LOOP; |
| } |
| } |
| } |
| } finally { |
| newContents.close(); |
| } |
| } finally { |
| oldContents.close(); |
| } |
| } |
| } finally { |
| temporaryFile.delete(); |
| resourcetoSave.eSetDeliver(true); |
| } |
| // CHECKSTYLE:ON |
| |
| // return !equal || resourcetoSave.getURI().toString().contains("StatusIndex"); |
| return !equal; |
| } |
| |
| /** |
| * Determine if a warning should be sent to the WorkspaceSession before saving the given resource and send |
| * this warning. |
| * <p> |
| * If a warning is sent, the WorkspaceSession will ignore the next modification made on the given |
| * resource. |
| * </p> |
| * |
| * @param resource |
| * the resource being saved |
| * @throws RepositoryConnectionException |
| * if a connection to the repository cannot be made |
| */ |
| private void treatSessionWarning(Resource resource) throws RepositoryConnectionException { |
| // If this adapter must warn the session about any saved resource |
| if (sendSessionWarningBeforeSaving) { |
| // We warn the session |
| ((WorkspaceSession)this.repository.getOrCreateSession()).addSavedResource(resource); |
| } else { |
| // If the given resource must be ignored (i.e is include in any of the |
| // resourcesToIgnorePaths) |
| if (isInResourcesToIgnorePath(resource)) { |
| // We warn the session |
| ((WorkspaceSession)this.repository.getOrCreateSession()).addSavedResource(resource); |
| } |
| } |
| } |
| |
| /** |
| * Indicates if the given resource is conform to any declared resourceToIgnore path. |
| * |
| * @param resource |
| * the resource to determine if it's conform to any declared resourceToIgnore path |
| * @return true if the given resource is conform to any declared resourceToIgnore path , false otherwise |
| */ |
| public boolean isInResourcesToIgnorePath(Resource resource) { |
| boolean isInResourceToIgnorePath = false; |
| Iterator<String> iterator = this.resourcesToIgnorePaths.iterator(); |
| while (iterator.hasNext() && !isInResourceToIgnorePath) { |
| String resourceToIgnorePath = iterator.next(); |
| isInResourceToIgnorePath = this.repository.isIncludedInPath(resourceToIgnorePath, resource); |
| } |
| return isInResourceToIgnorePath; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#undo() |
| */ |
| public void undo() throws ReadOnlyException { |
| // TODO accurate undo strategy |
| CommandStack commandStack = repository.getEditingDomain().getCommandStack(); |
| if (commandStack != null) { |
| commandStack.undo(); |
| } else { |
| // TODO ? |
| } |
| } |
| |
| /** |
| * Sets a predicate that will be used to determine which resource must not be unloaded when undoing |
| * changes. |
| * |
| * @param unloadableResourcePredicate |
| * a {@link Predicate} that returns true if the considered resource must not be unloaded when |
| * undoing changes, false otherwise |
| */ |
| public void setUnloadableResourcePredicate(Predicate<Resource> unloadableResourcePredicate) { |
| this.unloadableResourcePredicate = unloadableResourcePredicate; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#closeContext() |
| */ |
| public void closeContext() { |
| isReadOnlyContext = false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#attachSessionListenerForTypes(org.eclipse.mylyn.docs.intent.collab.handlers.notification.Notificator, |
| * java.util.Set) |
| */ |
| public void attachSessionListenerForTypes(Notificator typeNotificator, |
| Set<EStructuralFeature> listenedTypes) { |
| try { |
| |
| WorkspaceTypeListener typeListener = new WorkspaceTypeListener(typeNotificator, listenedTypes); |
| if (this.notificatorToListener.get(typeNotificator) == null) { |
| this.notificatorToListener.put(typeNotificator, new LinkedHashSet<WorkspaceTypeListener>()); |
| } |
| this.notificatorToListener.get(typeNotificator).add(typeListener); |
| ((WorkspaceSession)this.repository.getOrCreateSession()).addListener(typeListener); |
| } catch (RepositoryConnectionException e) { |
| // TODO handle properly such a repository connection exception |
| } |
| |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#detachSessionListenerForTypes(org.eclipse.mylyn.docs.intent.collab.handlers.notification.Notificator) |
| */ |
| public void detachSessionListenerForTypes(Notificator typeNotificator) { |
| try { |
| for (WorkspaceTypeListener listenerToRemove : this.notificatorToListener.get(typeNotificator)) { |
| ((WorkspaceSession)this.repository.getOrCreateSession()).removeListener(listenerToRemove); |
| } |
| |
| this.notificatorToListener.remove(typeNotificator); |
| } catch (RepositoryConnectionException e) { |
| // TODO handle properly such a repository connection exception |
| } |
| |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#allowChangeSubscriptionPolicy() |
| */ |
| public void allowChangeSubscriptionPolicy() { |
| // Nothing to do here |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getContext() |
| */ |
| public Object getContext() { |
| // No context to return as no context was created |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getResource(java.lang.String) |
| */ |
| public Resource getResource(String repositoryRelativePath) { |
| return getResource(repositoryRelativePath, true); |
| } |
| |
| /** |
| * Returns the resource located at the given path. |
| * |
| * @param repositoryRelativePath |
| * path of the searched resource (from the root of the repository) |
| * @param loadResourceOnDemand |
| * indicates if the resource should be loaded on demand or not |
| * @return the resource located at the given path |
| */ |
| public Resource getResource(String repositoryRelativePath, boolean loadResourceOnDemand) { |
| // We calculate the Repository URI corresponding to the given path |
| URI uri = this.repository.getURIMatchingPath(repositoryRelativePath); |
| final Resource resource = this.repository.getResourceSet().getResource(uri, loadResourceOnDemand); |
| return resource; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getOrCreateResource(java.lang.String) |
| */ |
| public Resource getOrCreateResource(String path) throws ReadOnlyException { |
| if (isReadOnlyContext) { |
| throw new ReadOnlyException( |
| "Cannot create a resource with a read-only context. The context should have been started with the 'openSaveContext' method."); |
| } |
| |
| // We calculate the Repository URI corresponding to the given path |
| URI uri = this.repository.getURIMatchingPath(path); |
| |
| // We first try to get the resource |
| Resource returnedResource = this.repository.getResourceSet().getResource(uri, false); |
| if (returnedResource == null) { |
| // If it doesn't exist, we create it |
| returnedResource = this.repository.getResourceSet().createResource(uri); |
| } else { |
| if (!returnedResource.isLoaded()) { |
| try { |
| returnedResource.load(getLoadOptions()); |
| } catch (IOException e) { |
| returnedResource = null; |
| } |
| } |
| } |
| return returnedResource; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getElementWithID(java.lang.Object) |
| */ |
| public EObject getElementWithID(Object uri) { |
| if (uri instanceof URI) { |
| EObject eObject = this.repository.getResourceSet().getEObject((URI)uri, true); |
| return eObject; |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getIDFromElement(org.eclipse.emf.ecore.EObject) |
| */ |
| public Object getIDFromElement(EObject element) { |
| URI uri = null; |
| if (element != null) { |
| uri = EcoreUtil.getURI(element); |
| |
| // if the URI starts with "#", it means that the resource is no longer part of the resource set. |
| // we return null to indicate the the given element is now invalid |
| if (uri.toString().startsWith("#")) { |
| return null; |
| } |
| } |
| return uri; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#attachRepositoryStructurer(org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryStructurer) |
| */ |
| public void attachRepositoryStructurer(RepositoryStructurer structurer) { |
| if (!(structurer instanceof RepositoryStructurer)) { |
| throw new IllegalArgumentException("Cannot attach " + structurer.getClass().getName() |
| + " to this adapter : should be " + RepositoryStructurer.class.getName()); |
| } |
| documentStructurer = structurer; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#setSendSessionWarningBeforeSaving(boolean) |
| */ |
| public void setSendSessionWarningBeforeSaving(boolean notifySessionBeforeSaving) { |
| this.sendSessionWarningBeforeSaving = notifySessionBeforeSaving; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#setSendSessionWarningBeforeSaving(java.util.Collection) |
| */ |
| public void setSendSessionWarningBeforeSaving(Collection<String> resourcesToIgnorePathList) { |
| this.sendSessionWarningBeforeSaving = false; |
| this.resourcesToIgnorePaths.clear(); |
| this.resourcesToIgnorePaths.addAll(resourcesToIgnorePathList); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#reload(org.eclipse.emf.ecore.EObject) |
| */ |
| public EObject reload(EObject elementToReload) { |
| EObject resolve = elementToReload; |
| if (elementToReload.eIsProxy()) { |
| resolve = EcoreUtil.resolve(elementToReload, this.repository.getResourceSet()); |
| } |
| return resolve; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#execute(org.eclipse.mylyn.docs.intent.collab.handlers.adapters.IntentCommand) |
| */ |
| public void execute(final IntentCommand command) { |
| // Step 1: create a recording command encapsulating the Intent command |
| final TransactionalEditingDomain editingDomain = repository.getEditingDomain(); |
| final RecordingCommand recordingCommand = new RecordingCommand(editingDomain) { |
| @Override |
| protected void doExecute() { |
| command.execute(); |
| } |
| }; |
| |
| // Step 2: make sure a command is not already running |
| final CommandStack commandStack = editingDomain.getCommandStack(); |
| // Check that the repository has not been disposed |
| if (commandStack != null) { |
| // Check that change recorder is not already recording |
| long timeout = System.currentTimeMillis(); |
| try { |
| while (((InternalTransactionalEditingDomain)editingDomain).getChangeRecorder().isRecording() |
| && System.currentTimeMillis() < timeout + TIMEOUT) { |
| try { |
| Thread.sleep(TIME_TO_WAIT_BEFORE_CHECKING_SESSIONDELTA); |
| } catch (InterruptedException e) { |
| // Command will be executed |
| } |
| } |
| |
| // Step 3: execute command |
| commandStack.execute(recordingCommand); |
| } catch (NullPointerException e) { |
| // can happen when TED is disposed |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getResourcePath(org.eclipse.emf.common.util.URI) |
| */ |
| public String getResourcePath(URI resourceURI) { |
| if (isRepositoryResource(resourceURI)) { |
| String resourcePath = resourceURI.toString(); |
| resourcePath = resourcePath.replace("platform:/resource", "").replace( |
| this.repository.getWorkspaceConfig().getRepositoryAbsolutePath(), ""); |
| if (this.repository.shouldHaveWorkspaceResourceExtension(resourcePath)) { |
| resourcePath = resourcePath.replace("." + resourceURI.fileExtension(), ""); |
| } |
| return resourcePath; |
| } |
| return null; |
| } |
| |
| /** |
| * Indicates if the given URI describe a resource contained in the repository. |
| * |
| * @param resourceURI |
| * the resource URI |
| * @return true if the given URI describe a resource contained in the repository, false otherwise |
| */ |
| private boolean isRepositoryResource(URI resourceURI) { |
| return resourceURI.toString().contains( |
| this.repository.getWorkspaceConfig().getRepositoryAbsolutePath()); |
| } |
| |
| /** |
| * Removes the dangling references contained in the given resource and saves the resource (without |
| * notifying other clients). |
| * |
| * @param resource |
| * the resource to clean |
| * @return true if the given resource contained dangling references (and hence was cleaned and saved), |
| * false otherwise |
| * @throws SaveException |
| * if resource cannot be save |
| * @throws RepositoryConnectionException |
| * if repository cannot be accessed |
| */ |
| private boolean removeDanglingElements(Resource resource) throws SaveException, |
| RepositoryConnectionException { |
| |
| // Step 1: detect dangling references |
| Collection<EObject> objectsToRemove = Sets.newLinkedHashSet(); |
| Iterator<EObject> iterator = resource.getContents().iterator(); |
| while (iterator.hasNext()) { |
| EObject root = iterator.next(); |
| if (root.eContainer() != null && root.eContainer().eResource() == null) { |
| objectsToRemove.add(root); |
| } |
| } |
| |
| try { |
| if (!objectsToRemove.isEmpty()) { |
| // Step 2: save or delete the resource if empty |
| treatSessionWarning(resource); |
| if (resource.getContents().size() <= objectsToRemove.size()) { |
| resource.delete(getSaveOptions()); |
| } else { |
| for (EObject objectToRemove : objectsToRemove) { |
| EcoreUtil.remove(objectToRemove); |
| } |
| for (EObject nonDanglingElement : resource.getContents()) { |
| new RemoveDanglingReferences(this.repository.getEditingDomain(), nonDanglingElement) |
| .execute(); |
| } |
| resource.save(getSaveOptions()); |
| } |
| return true; |
| } else { |
| for (EObject nonDanglingElement : resource.getContents()) { |
| new RemoveDanglingReferences(this.repository.getEditingDomain(), nonDanglingElement) |
| .execute(); |
| } |
| } |
| } catch (IOException ioE) { |
| throw new SaveException(ioE.getMessage()); |
| } |
| return false; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getRepository() |
| */ |
| public Repository getRepository() { |
| return this.repository; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.mylyn.docs.intent.collab.handlers.adapters.RepositoryAdapter#getResourceSet() |
| */ |
| public ResourceSet getResourceSet() { |
| return new ResourceSetImpl(); |
| } |
| } |