/******************************************************************************* * Copyright (c) 2013, 2015 Obeo and others. * 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 * Alexandra Buzila - bug 469105 *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.internal.logical; import static org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil.adaptAs; import static org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil.findFile; import java.io.File; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import org.eclipse.compare.ISharedDocumentAdapter; import org.eclipse.compare.IStreamContentAccessor; import org.eclipse.compare.ITypedElement; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.compare.ide.internal.utils.StoragePathAdapter; import org.eclipse.emf.compare.ide.utils.IStoragePathAdapterProvider; import org.eclipse.emf.compare.ide.utils.IStoragePathProvider; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.history.IFileRevision; import org.eclipse.team.core.variants.CachedResourceVariant; import org.eclipse.team.core.variants.IResourceVariant; import org.eclipse.ui.IEditorInput; /** * This implementation of IStorage simply wraps a stream content accessor. * * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> */ public class StreamAccessorStorage implements IStorage, IStoragePathAdapterProvider { /** The wrapped accessor. */ private final IStreamContentAccessor accessor; /** Full path of the underlying content. */ private final String fullPath; /** * Wraps the given accessor. * * @param accessor * The accessor to wrap as an IStorage. * @param fullPath * Full path to the underlying storage. */ public StreamAccessorStorage(IStreamContentAccessor accessor, String fullPath) { this.accessor = accessor; this.fullPath = fullPath; } /** * This is a short-hand for {@link #fromTypedElement(String, ITypedElement)}. This second one should be * preferred in case the given element is remote and we need a proper path for it. * * @param element * The typed element for which we need to create a wrapper. * @return The wrapped typed element. * @throws IllegalArgumentException * If the given element does not implement {@link IStreamContentAccessor}. */ public static StreamAccessorStorage fromTypedElement(ITypedElement element) throws IllegalArgumentException { return fromTypedElement(null, element); } /** * Creates a StreamAccessorStorage given the input typed element. Note that the given typed element -must- * implement {@link IStreamContentAccessor} as well. * * @param storagePath * The full path to this storage, can be <code>null</code>. * @param element * The typed element for which we need to create a wrapper. * @return The wrapped typed element. * @throws IllegalArgumentException * If the given element does not implement {@link IStreamContentAccessor}. */ public static StreamAccessorStorage fromTypedElement(String storagePath, ITypedElement element) throws IllegalArgumentException { if (!(element instanceof IStreamContentAccessor)) { throw new IllegalArgumentException(); } final String fullPath; if (storagePath != null) { fullPath = storagePath; } else { fullPath = findPath(element); } return new StreamAccessorStorage((IStreamContentAccessor)element, fullPath); } /** * This will try to find a path for the given typed element. If that element can be adapted, it delegates * to {@link #findPath(IAdaptable)}. * * @param element * The element for which we need a path. * @return A path for the given element. */ private static String findPath(ITypedElement element) { final String fullPath; final IFile file = findFile(element); if (file != null) { fullPath = file.getFullPath().toString(); } else { final IFileRevision revision = findFileRevision(element); String tmp = null; if (revision != null) { final URI uri = revision.getURI(); if (uri != null) { tmp = org.eclipse.emf.common.util.URI.decode(uri.toString()); } else if (revision instanceof IAdaptable) { tmp = findPath((IAdaptable)revision); } } if (tmp != null) { fullPath = tmp; } else { // We can't do much here... fullPath = element.getName(); } } return fullPath; } /** * Get the commit id for the given element * * @param element * The element for which we want the commit id * @return the commit id */ public static IFileRevision findCommitId(ITypedElement element) { return findFileRevision(element); } /** * {@inheritDoc} * * @see IStoragePathAdapterProvider#createStoragePathAdapter() */ public Adapter createStoragePathAdapter(String path, boolean isLocal) { IFileRevision findFileRevision = findFileRevision((ITypedElement)accessor); IFile file = findFile((ITypedElement)accessor); // The parameter isLocal is overwritten because more informations are available here to determine // whether the file is local or remote boolean local = file != null; if (!isLocal && findFileRevision != null) { return new StoragePathAdapter(path, local, findFileRevision.getContentIdentifier(), findFileRevision.getAuthor()); } else { return new StoragePathAdapter(path, local); } } /** * If the {@code adaptable} can be adapted to an {@link IResourceVariant}, we'll try to retrieve the * resource's path. It will use {@link IStoragePathProvider}s, if any are registered. May return null * * @param adaptable * The {@link IAdaptable} for which we need a path * @return A path for the given element. */ private static String findPath(IAdaptable adaptable) { String result = null; final IResourceVariant variant = (IResourceVariant)adaptable.getAdapter(IResourceVariant.class); if (variant != null) { try { final IStorage storage = variant.getStorage(new NullProgressMonitor()); if (storage != null) { final Object adapter = Platform.getAdapterManager().loadAdapter(storage, IStoragePathProvider.class.getName()); if (adapter instanceof IStoragePathProvider) { final IPath fixedPath = ((IStoragePathProvider)adapter).computeFixedPath(storage); result = fixedPath.toString(); } else if (storage instanceof IFile) { result = storage.getFullPath().toString(); } } else if (variant instanceof CachedResourceVariant) { result = ((CachedResourceVariant)variant).getDisplayPath().toString(); } } catch (TeamException e) { // Swallow, this was a best effort... } } return result; } /** * {@inheritDoc} * * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ // suppressing since overriding. @SuppressWarnings({"rawtypes", "unchecked" }) public Object getAdapter(Class adapter) { Object adapted = null; if (adapter.isInstance(this)) { adapted = this; } else if (adapter == IStreamContentAccessor.class) { adapted = accessor; } else if (accessor instanceof ITypedElement) { if (adapter == ITypedElement.class) { adapted = accessor; } else if (adapter.isAssignableFrom(IFile.class)) { adapted = findFile((ITypedElement)accessor); } } return adapted; } /** * {@inheritDoc} * * @see org.eclipse.core.resources.IStorage#getContents() */ public InputStream getContents() throws CoreException { return accessor.getContents(); } /** * {@inheritDoc} * * @see org.eclipse.core.resources.IStorage#getFullPath() */ public IPath getFullPath() { return new Path(fullPath); } /** * {@inheritDoc} * * @see org.eclipse.core.resources.IStorage#getName() */ public String getName() { if (accessor instanceof ITypedElement) { return ((ITypedElement)accessor).getName(); } return getFullPath().lastSegment(); } /** * {@inheritDoc} * * @see org.eclipse.core.resources.IStorage#isReadOnly() */ public boolean isReadOnly() { if (accessor instanceof ITypedElement) { final IFile file = findFile((ITypedElement)accessor); if (file != null) { return file.isReadOnly(); } } final File file = getFullPath().toFile(); return !file.exists() || !file.canWrite(); } /** * Try and determine the file revision of the given element. * * @param element * The element for which we need an {@link IFileRevision}. * @return The file revision of the given element if we could find one, <code>null</code> otherwise. */ private static IFileRevision findFileRevision(ITypedElement element) { if (element == null) { return null; } // Can we adapt it directly? IFileRevision revision = adaptAs(element, IFileRevision.class); if (revision == null) { // Quite the workaround... but CVS does not offer us any other way. // These few lines of code is what make us depend on org.eclipse.ui... Can we find another way? final ISharedDocumentAdapter documentAdapter = adaptAs(element, ISharedDocumentAdapter.class); if (documentAdapter != null) { final IEditorInput editorInput = documentAdapter.getDocumentKey(element); if (editorInput != null) { revision = adaptAs(editorInput, IFileRevision.class); } } } if (revision == null) { // Couldn't do it the API way ... // At the time of writing, this was the case with EGit try { final Method method = element.getClass().getMethod("getFileRevision"); //$NON-NLS-1$ final Object value = method.invoke(element); if (value instanceof IFileRevision) { revision = (IFileRevision)value; } } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { // Ignore exceptions } } return revision; } }