Bug 549634 - View model editor should support opening files from history

The view model editor can now open view models from a history revision.
If this is the case, the view model is copied to a temporary file to
allow migration before showing it in the editor.
View models opened from the history are shown as readonly. For this the
tree master detail renderer was adapted to NOT offer drag and drop or a
context menu if the tree is configured as readonly

Also added a RCPTT test testing the opening from history inluding
migration.

Change-Id: Ifbe35d0e7470fa1d9bbc9de1999f6ca7c9c20092
Signed-off-by: Lucas Koehler <lkoehler@eclipsesource.com>
diff --git a/bundles/org.eclipse.emf.ecp.ide.editor.view/META-INF/MANIFEST.MF b/bundles/org.eclipse.emf.ecp.ide.editor.view/META-INF/MANIFEST.MF
index f3b0294..0a48667 100644
--- a/bundles/org.eclipse.emf.ecp.ide.editor.view/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.emf.ecp.ide.editor.view/META-INF/MANIFEST.MF
@@ -21,7 +21,8 @@
  org.eclipse.emf.ecp.view.migrator;bundle-version="[1.22.0,1.23.0)",
  org.eclipse.emfforms.core.services;bundle-version="[1.22.0,1.23.0)",
  org.eclipse.emf.edit.ui;bundle-version="[2.7.0,3.0.0)",
- org.eclipse.emfforms.ide.view.segments;bundle-version="[1.22.0,1.23.0)"
+ org.eclipse.emfforms.ide.view.segments;bundle-version="[1.22.0,1.23.0)",
+ org.eclipse.emfforms.editor;bundle-version="[1.22.0,1.23.0)"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Bundle-ActivationPolicy: lazy
 Import-Package: org.eclipse.e4.core.contexts;version="[1.3.0,2.0.0)",
diff --git a/bundles/org.eclipse.emf.ecp.ide.editor.view/src/org/eclipse/emf/ecp/ide/editor/view/ViewEditorPart.java b/bundles/org.eclipse.emf.ecp.ide.editor.view/src/org/eclipse/emf/ecp/ide/editor/view/ViewEditorPart.java
index 14037dc..de4e9af 100644
--- a/bundles/org.eclipse.emf.ecp.ide.editor.view/src/org/eclipse/emf/ecp/ide/editor/view/ViewEditorPart.java
+++ b/bundles/org.eclipse.emf.ecp.ide.editor.view/src/org/eclipse/emf/ecp/ide/editor/view/ViewEditorPart.java
@@ -16,8 +16,11 @@
 
 import static org.eclipse.emf.ecp.view.spi.context.ViewModelContextFactory.provide;
 
+import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -27,6 +30,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import org.eclipse.core.resources.IFile;
@@ -35,6 +39,7 @@
 import org.eclipse.core.resources.IResourceChangeListener;
 import org.eclipse.core.resources.IResourceDelta;
 import org.eclipse.core.resources.IResourceDeltaVisitor;
+import org.eclipse.core.resources.IStorage;
 import org.eclipse.core.resources.ResourcesPlugin;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -80,7 +85,9 @@
 import org.eclipse.emf.edit.domain.IEditingDomainProvider;
 import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
 import org.eclipse.emf.edit.ui.util.EditUIUtil;
+import org.eclipse.emfforms.spi.common.report.AbstractReport;
 import org.eclipse.emfforms.spi.editor.GotoMarkerAdapter;
+import org.eclipse.emfforms.spi.editor.helpers.ResourceSetHelpers;
 import org.eclipse.emfforms.spi.ide.view.segments.DmrToSegmentsMigrationException;
 import org.eclipse.emfforms.spi.ide.view.segments.DmrToSegmentsMigrator;
 import org.eclipse.emfforms.spi.ide.view.segments.ToolingModeUtil;
@@ -96,6 +103,8 @@
 import org.eclipse.ui.IEditorPart;
 import org.eclipse.ui.IEditorSite;
 import org.eclipse.ui.IPartListener2;
+import org.eclipse.ui.IPathEditorInput;
+import org.eclipse.ui.IStorageEditorInput;
 import org.eclipse.ui.IWorkbenchPage;
 import org.eclipse.ui.IWorkbenchPartReference;
 import org.eclipse.ui.PartInitException;
@@ -119,6 +128,7 @@
 public class ViewEditorPart extends EditorPart implements
 	ViewModelEditorCallback, IEditingDomainProvider {
 
+	private URI inputUri;
 	private Resource resource;
 	private BasicCommandStack basicCommandStack;
 	private Composite parent;
@@ -321,8 +331,12 @@
 	 */
 	private void loadView(boolean migrate, boolean resolve) throws IOException, PartInitException {
 
-		// resourceURI must be a platform resource URI
-		final URI resourceURI = EditUIUtil.getURI(getEditorInput(), editingDomain.getResourceSet().getURIConverter());
+		if (inputUri == null) {
+			inputUri = getInputUri(getEditorInput())
+				.orElseThrow(() -> new PartInitException("Invalid editor input.")); //$NON-NLS-1$
+		}
+
+		final URI resourceURI = inputUri;
 		if (migrate) {
 			checkMigration(resourceURI);
 		}
@@ -334,15 +348,55 @@
 		if (resolve) {
 			// resolve all proxies
 			final ResourceSet resourceSet = editingDomain.getResourceSet();
-			int rsSize = resourceSet.getResources().size();
-			EcoreUtil.resolveAll(resourceSet);
-			while (rsSize != resourceSet.getResources().size()) {
-				EcoreUtil.resolveAll(resourceSet);
-				rsSize = resourceSet.getResources().size();
-			}
+			ResourceSetHelpers.resolveAllProxies(resourceSet);
 		}
 	}
 
+	/**
+	 * Gets a uri from the given {@link IEditorInput}. If the editor input is a {@link IPathEditorInput} or a
+	 * {@link IStorageEditorInput} whose {@link IStorage} has a path, the uri is directly derived from the
+	 * input. In case of a {@link IStorageEditorInput} without a path, a temporary file is created which contains the
+	 * storage's contents.
+	 * In any other case, an empty Optional is returned.
+	 *
+	 * @param editorInput The editor's input
+	 * @return The File containing the editor inputs contents if possible, nothing otherwise
+	 */
+	private Optional<URI> getInputUri(IEditorInput editorInput) {
+		if (isEditable(editorInput)) {
+			// Normal file that can be edited on the hard drive
+			return Optional.of(EditUIUtil.getURI(getEditorInput()));
+		} else if (editorInput instanceof IStorageEditorInput) {
+			try {
+				final IStorage storage = IStorageEditorInput.class.cast(editorInput).getStorage();
+				// Create a temporary file and copy the storage's content to it.
+				final File tempFile = File.createTempFile("view-", ".tmp.view"); //$NON-NLS-1$ //$NON-NLS-2$
+				tempFile.delete();
+				tempFile.deleteOnExit();
+				try (InputStream contents = storage.getContents()) {
+					Files.copy(contents, tempFile.toPath());
+					return Optional.of(URI.createFileURI(tempFile.getAbsolutePath()));
+				}
+			} catch (final CoreException | IOException ex) {
+				Activator.getDefault().getReportService().report(new AbstractReport(ex));
+				return Optional.empty();
+			}
+
+		}
+		return Optional.empty();
+	}
+
+	/**
+	 * Returns whether the editor input allows editing of its contents.
+	 *
+	 * @param editorInput the editor's {@link IEditorInput}
+	 * @return <code>true</code> if the input source allows editing, <code>false</code> otherwise
+	 */
+	private boolean isEditable(IEditorInput editorInput) {
+		// Only allow editing data if it can be persisted
+		return editorInput.getPersistable() != null;
+	}
+
 	private void checkMigration(final URI resourceURI) {
 		final ViewModelMigrator migrator = ViewModelMigratorUtil.getViewModelMigrator();
 		if (migrator == null) {
@@ -712,6 +766,11 @@
 				.createViewModelContext(ViewProviderHelper.getView(view, null), view,
 					provide(new DefaultReferenceService(), new EMFDeleteServiceImpl()),
 					contextValues);
+
+			if (!isEditable(getEditorInput())) {
+				viewModelContext.getViewModel().setReadonly(true);
+			}
+
 			viewModelContext.putContextValue("enableMultiEdit", Boolean.TRUE); //$NON-NLS-1$
 			render = ECPSWTViewRenderer.INSTANCE.render(parent, viewModelContext);
 			getSite().setSelectionProvider(
diff --git a/bundles/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailSWTRenderer.java b/bundles/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailSWTRenderer.java
index 41c2de6..7afb40c 100644
--- a/bundles/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailSWTRenderer.java
+++ b/bundles/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailSWTRenderer.java
@@ -505,7 +505,7 @@
 	 * @return true if a context menu should be shown, false otherwise
 	 */
 	protected boolean hasContextMenu() {
-		return true;
+		return !getVElement().isEffectivelyReadonly();
 	}
 
 	/**
@@ -514,7 +514,7 @@
 	 * @return true if DnD should be supported , false otherwise
 	 */
 	protected boolean hasDnDSupport() {
-		return true;
+		return !getVElement().isEffectivelyReadonly();
 	}
 
 	/**
diff --git a/tests/ECPQ7Tests/EPPTests/update-site/project/ViewEditor_openFromHistory.test b/tests/ECPQ7Tests/EPPTests/update-site/project/ViewEditor_openFromHistory.test
new file mode 100644
index 0000000..8ff4986
--- /dev/null
+++ b/tests/ECPQ7Tests/EPPTests/update-site/project/ViewEditor_openFromHistory.test
@@ -0,0 +1,49 @@
+--- RCPTT testcase ---
+Format-Version: 1.0
+Contexts: _ihvnUC3mEeSwhO5Nwx0hPg,_D4Pj4C3lEeSwhO5Nwx0hPg
+Element-Name: ViewEditor_openFromHistory
+Element-Type: testcase
+Element-Version: 3.0
+External-Reference: 
+Id: _7uz1YLIIEemg6Z8M9T7uKw
+Runtime-Version: 2.5.0.201907120000
+Save-Time: 7/29/19 5:01 PM
+Testcase-Type: ecl
+
+------=_.content-0a7243a0-75d3-3d5f-9791-539de0e5b7ac
+Content-Type: text/ecl
+Entry-Name: .content
+
+// Edit view model to require migration
+get-view "Project Explorer" | get-tree | select "org.eclipse.emf.ecp.makeithappen.model.viewmodel/viewmodels/User.view" 
+    | get-menu -path "Open With/Text Editor" | click
+with [get-editor "User.view" | get-text-viewer] {
+    set-caret-pos 2 463
+    key-type Del
+    type-text 7
+    key-type "M1+s"
+}
+
+// Edit back for current revision
+with [get-editor "User.view" | get-text-viewer] {
+    set-caret-pos 2 465
+    set-caret-pos 2 463
+    key-type Del
+    type-text 8
+    key-type "M1+s"
+}
+
+// Open history revision, migrate it, and check that it's readonly
+get-editor "User.view" | close
+get-view "Project Explorer" | get-tree | select "org.eclipse.emf.ecp.makeithappen.model.viewmodel/viewmodels/User.view" 
+    | get-menu -path "Team/Show Local History" | click
+get-view History | get-tree | select [get-item -path ".*" -index 1] | get-menu 
+    -path "Open With/View Model Editor" | click
+get-window "Perform migration to the latest view model version?" | get-button Yes | click
+get-window "Migrate view models from workspace" | get-button No | click
+get-editor "User.view" | get-editbox -after [get-label Name] | get-property enablement | equals false | verify-true
+
+
+
+
+------=_.content-0a7243a0-75d3-3d5f-9791-539de0e5b7ac--
diff --git a/tests/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt.test/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailRenderer_PTest.java b/tests/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt.test/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailRenderer_PTest.java
index db05451..80a4da2 100644
--- a/tests/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt.test/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailRenderer_PTest.java
+++ b/tests/org.eclipse.emf.ecp.view.treemasterdetail.ui.swt.test/src/org/eclipse/emf/ecp/view/spi/treemasterdetail/ui/swt/TreeMasterDetailRenderer_PTest.java
@@ -266,7 +266,8 @@
 		final Control[] content = getDetailContent(detail);
 		assertFalse(content[2].isEnabled());
 
-		assertContextMenu(tree, 0);
+		// Read-only tree should not have any context menu
+		assertNull(tree.getMenu());
 	}
 
 	@Test