1 /*******************************************************************************
2 * Copyright (c) 2011, 2012 Obeo.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * Obeo - initial API and implementation
10 *******************************************************************************/
11 package org
.eclipse
.emf
.compare
.ide
.ui
.logical
;
13 import com
.google
.common
.annotations
.Beta
;
15 import java
.io
.IOException
;
16 import java
.io
.InputStream
;
17 import java
.net
.URISyntaxException
;
20 import org
.eclipse
.core
.resources
.IFile
;
21 import org
.eclipse
.core
.resources
.IFileState
;
22 import org
.eclipse
.core
.resources
.IProject
;
23 import org
.eclipse
.core
.resources
.IResource
;
24 import org
.eclipse
.core
.resources
.IStorage
;
25 import org
.eclipse
.core
.resources
.IWorkspaceRoot
;
26 import org
.eclipse
.core
.resources
.ResourcesPlugin
;
27 import org
.eclipse
.core
.runtime
.CoreException
;
28 import org
.eclipse
.core
.runtime
.IPath
;
29 import org
.eclipse
.core
.runtime
.IStatus
;
30 import org
.eclipse
.core
.runtime
.NullProgressMonitor
;
31 import org
.eclipse
.core
.runtime
.Path
;
32 import org
.eclipse
.core
.runtime
.Status
;
33 import org
.eclipse
.emf
.common
.util
.URI
;
34 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.EMFCompareIDEUIPlugin
;
35 import org
.eclipse
.emf
.compare
.ide
.utils
.StorageURIConverter
;
36 import org
.eclipse
.emf
.ecore
.resource
.URIConverter
;
37 import org
.eclipse
.team
.core
.RepositoryProvider
;
38 import org
.eclipse
.team
.core
.history
.IFileHistory
;
39 import org
.eclipse
.team
.core
.history
.IFileHistoryProvider
;
40 import org
.eclipse
.team
.core
.history
.IFileRevision
;
43 * This {@link URIConverter} will be used in order to fetch remote contents instead of local contents when
46 * Let's suppose we are trying to compare "ecore.genmodel". This file depends on "ecore.ecore". However,
47 * "ecore.ecore" evolves more than its corresponding genmodel since all changes made in this file are not
48 * "breaking" for the genmodel. For example, version <i>1.19</i> of the genmodel was commited at the same time
49 * as version <i>1.17</i> of the ecore. If I compare my own copy of the genmodel with the "latest from HEAD"
50 * (version <i>1.19</i>), then this URI Converter will be used to resolve the proxy of that "remote" file with
51 * the corresponding "remote" version of the ecore file. In this case, it should be version <i>1.20</i>
52 * (latest non-breaking) of the ecore file, not the <i>1.17</i> that was commited along.
55 * To this end, when creating a revisioned URI converter, we give it a file revision that corresponds to what
56 * the user asked to compare, in the above example, the "latest from HEAD" of ecore.genmodel. We will fetch
57 * the revision that is "above" that one. In the case of the "latest", there is none. if we had been given
58 * version <i>1.18</i>, we would consider version <i>1.19</i>... And consider this "younger" revision to be
59 * the "upper bound" of the revisions we'll resolve for dependent fragments (in the example above,
63 * This resolution strategy means that we assume the users never broke their logical model by committing some
64 * fragments without the others (i.e. they never committed "ecore.ecore" without committing the
65 * "ecore.genmodel" if there were breaking changes made).
68 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
71 public final class RevisionedURIConverter
extends StorageURIConverter
{
72 /** The revision of the base resource. This revision's timestamp will be used to resolve proxies. */
73 private IFileRevision baseRevision
;
75 /** The actual Ifile from which was extracted {@link #baseRevision}. */
76 private IFile baseFile
;
79 * We need a "maximum" timestamp : see javadoc of the class.
82 * <li><code>-2</code> is the unitialized value,</li>
83 * <li><code>-1</code> means we have no upper bound (use the latest revisions of the files),</li>
84 * <li>Any other value will be used to find the file revisions of our dependencies.</li>
87 private long maxTimestamp
= -2;
90 * Instantiates our URI converter given its delegate.
93 * Our delegate URI converter.
95 * The base revision against which this URI converter should resolve URIs.
97 public RevisionedURIConverter(URIConverter delegate
, IFileRevision baseRevision
) {
99 this.baseRevision
= baseRevision
;
100 this.baseFile
= findFile(getFileURI(baseRevision
).toString());
103 getLoadedRevisions().add(baseRevision
.getStorage(new NullProgressMonitor()));
104 } catch (CoreException e
) {
105 // We cannot find the storage of our base revision?
108 final RepositoryProvider repositoryProvider
= RepositoryProvider
.getProvider(baseFile
.getProject());
109 if (repositoryProvider
== null) {
113 final IFileHistoryProvider historyProvider
= repositoryProvider
.getFileHistoryProvider();
114 final IFileHistory history
= historyProvider
.getFileHistoryFor(baseFile
, IFileHistoryProvider
.NONE
,
115 new NullProgressMonitor());
116 if (history
!= null) {
117 // We'll search for the revision "above" the given one in order to determine our upper bound
118 final IFileRevision
[] revisions
= history
.getFileRevisions();
119 final String baseID
= baseRevision
.getContentIdentifier();
121 for (int i
= 0; i
< revisions
.length
&& this.maxTimestamp
== -2; i
++) {
122 if (baseID
.equals(revisions
[i
].getContentIdentifier())) {
124 this.maxTimestamp
= -1;
126 this.maxTimestamp
= revisions
[i
- 1].getTimestamp();
136 * @see org.eclipse.emf.compare.util.DelegatingURIConverter#createInputStream(org.eclipse.emf.common.util.URI,
139 @SuppressWarnings("resource")
141 public InputStream
createInputStream(URI uri
, Map
<?
, ?
> options
) throws IOException
{
142 InputStream stream
= null;
144 final URI normalizedUri
= normalize(uri
);
145 // If this uri points to the plugins directory, load it directly
146 if (normalizedUri
.isPlatformPlugin() || normalizedUri
.toString().matches("(\\.\\./)+?plugins/.*")) { //$NON-NLS-1$
147 stream
= super.createInputStream(normalizedUri
, options
);
149 // Otherwise, load it from the repository (resource might not yet (or no longer) exist locally)
150 final IResource targetFile
;
151 if (normalizedUri
.isPlatform()) {
152 IPath platformString
= new Path(normalizedUri
.trimFragment().toPlatformString(true));
153 IResource temp
= ResourcesPlugin
.getWorkspace().getRoot().getFile(platformString
);
154 if (!temp
.exists() && normalizedUri
.isPlatformResource() && platformString
.segmentCount() > 1) {
155 // We tend to get here with unresolvable URIs with git; as it tends to give URIs of the
157 // platform:/resource/<repository name>/<workspace relative path> instead of the
159 // platform:/resource/<workspace relative path> . We'll try for this case
160 targetFile
= ResourcesPlugin
.getWorkspace().getRoot().getFile(
161 platformString
.removeFirstSegments(1));
167 * FIXME Deresolve the URI against the workspace root, if it cannot be done, delegate to
168 * super.createInputStream()
170 targetFile
= ResourcesPlugin
.getWorkspace().getRoot().getFile(
171 new Path(normalizedUri
.trimFragment().toString()));
174 if (targetFile
!= null) {
175 stream
= openRevisionStream(targetFile
);
177 // FIXME The file URI couldn't be resolved in the workspace...
180 if (stream
== null) {
181 return super.createInputStream(uri
, options
);
189 * Opens an input stream on the contents of the first revision of the file designed by <em>targetURI</em>
190 * which timestamp is inferior or equal to that of {@link #baseRevision}. If no such revision exist, we'll
191 * use the closest to the {@link #baseRevision}'s timestamp we can find, hoping that it does correspond to
192 * the sought revision.
194 * Take good note that the <em>targetFile</em> may not exist locally. This handle will only serve in order
195 * to retrieve its repository provider.
199 * The resource we seek a revision of.
200 * @return The opened input stream. May be <code>null</code> if we failed to open it.
202 @SuppressWarnings("resource")
203 private InputStream
openRevisionStream(IResource targetFile
) {
204 IResource actualFile
= targetFile
;
205 if (!actualFile
.exists()) {
206 // Can we relativize its path according to the baseRevision?
207 actualFile
= findFile(actualFile
.getFullPath().toString());
208 if (actualFile
== null) {
209 actualFile
= targetFile
;
213 InputStream stream
= null;
214 final RepositoryProvider repositoryProvider
= RepositoryProvider
.getProvider(actualFile
.getProject());
216 if (repositoryProvider
!= null) {
217 final IFileHistoryProvider historyProvider
= repositoryProvider
.getFileHistoryProvider();
218 final IFileHistory history
= historyProvider
.getFileHistoryFor(actualFile
,
219 IFileHistoryProvider
.NONE
, new NullProgressMonitor());
221 if (history
!= null) {
222 // This file exists on the repository.
223 IFileRevision soughtRevision
= null;
224 final IFileRevision
[] revisions
= history
.getFileRevisions();
225 for (int i
= 0; i
< revisions
.length
&& soughtRevision
== null; i
++) {
226 final IFileRevision revision
= revisions
[i
];
227 if (maxTimestamp
< 0 || revision
.getTimestamp() < maxTimestamp
) {
228 soughtRevision
= revision
;
232 if (soughtRevision
!= null) {
234 IStorage storage
= soughtRevision
.getStorage(new NullProgressMonitor());
235 getLoadedRevisions().add(storage
);
236 stream
= storage
.getContents();
237 } catch (CoreException e
) {
238 // failed to retrieve revision contents
245 if (stream
== null) {
246 // Either this file is not connected to a repository, or we failed to retrieve a revision.
247 // Search through local history.
249 IFileState soughtState
= null;
250 final IFileState
[] revisions
= ((IFile
)actualFile
).getHistory(new NullProgressMonitor());
251 for (int i
= 0; i
< revisions
.length
&& soughtState
== null; i
++) {
252 final IFileState revision
= revisions
[i
];
253 if (maxTimestamp
< 0 || revision
.getModificationTime() < maxTimestamp
) {
254 soughtState
= revision
;
258 if (soughtState
!= null) {
259 getLoadedRevisions().add(soughtState
);
260 stream
= soughtState
.getContents();
262 getLoadedRevisions().add((IFile
)actualFile
);
263 stream
= ((IFile
)actualFile
).getContents();
265 } catch (CoreException e
) {
266 // failed to retrieve local contents
275 * Tries and find an IFile corresponding to the given path.
278 * The path for which we need an IFile.
279 * @return The IFile for the given path if we could find it, <code>null</code> otherwise.
281 private IFile
findFile(String path
) {
282 final java
.net
.URI baseURI
= getFileURI(baseRevision
);
283 java
.net
.URI targetURI
= convertToURI(path
);
284 java
.net
.URI relativizedURI
= baseURI
.relativize(targetURI
);
285 final IPath relativizedPath
;
286 if (relativizedURI
.getPath().length() == 0) {
287 relativizedPath
= new Path(targetURI
.getPath());
289 relativizedPath
= new Path(relativizedURI
.getPath());
291 final String
[] pathSegments
= relativizedPath
.segments();
294 * 'Repository' URIs cannot be found directly in the workspace : they are relative to the repository
295 * location. Let's try to find the IProject in which this file is located ...
297 final IWorkspaceRoot root
= ResourcesPlugin
.getWorkspace().getRoot();
298 IProject container
= null;
300 int projectIndex
= 0;
301 for (; projectIndex
< pathSegments
.length
&& (container
== null || !container
.exists()); projectIndex
++) {
302 container
= root
.getProject(pathSegments
[projectIndex
]);
306 if (container
!= null && container
.exists()) {
307 final IPath filePath
= relativizedPath
.removeFirstSegments(projectIndex
);
308 file
= container
.getFile(filePath
);
314 private java
.net
.URI
getFileURI(IFileRevision revision
) {
315 final java
.net
.URI baseURI
= revision
.getURI();
316 if (baseURI
!= null) {
320 java
.net
.URI result
= null;
322 final IStorage storage
= revision
.getStorage(new NullProgressMonitor());
323 final String name
= revision
.getName();
325 IPath path
= storage
.getFullPath();
326 final String lastSegment
= path
.lastSegment();
327 if (!lastSegment
.equals(name
)) {
328 final int nameIndex
= lastSegment
.indexOf(name
);
329 if (nameIndex
!= -1) {
330 path
= path
.removeLastSegments(1);
331 path
= path
.append(lastSegment
.substring(0, nameIndex
+ name
.length()));
335 result
= new java
.net
.URI(path
.toString());
336 } catch (CoreException e
) {
338 } catch (URISyntaxException e
) {
345 * Silently converts the given {@code path} into an {@link java.net.URI}.
348 * The path for which we need a java URI.
349 * @return The converted URI if the path could be parsed as a valid URI, <code>null</code> otherwise.
351 private java
.net
.URI
convertToURI(String path
) {
353 return new java
.net
.URI(path
);
354 } catch (URISyntaxException e
) {
360 * Logs the given exception as an error.
363 * The exception we need to log.
365 private static void logError(Exception e
) {
366 final IStatus status
= new Status(IStatus
.ERROR
, EMFCompareIDEUIPlugin
.PLUGIN_ID
, e
.getMessage(), e
);
367 EMFCompareIDEUIPlugin
.getDefault().getLog().log(status
);