/******************************************************************************* * Copyright (c) 2015 Willink Transformations 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: * E.D.Willink - initial API and implementation *******************************************************************************/ package org.eclipse.ocl.xtext.base.ui.model; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IUndoManager; import org.eclipse.jface.text.TextViewer; import org.eclipse.ocl.xtext.base.ui.BaseEditor; import org.eclipse.ocl.xtext.base.ui.BaseUiPluginHelper; import org.eclipse.ocl.xtext.base.ui.messages.BaseUIMessages; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.ui.editor.model.XtextDocument; import org.eclipse.xtext.ui.editor.model.XtextDocumentProvider; import org.eclipse.xtext.util.concurrent.IUnitOfWork; /** * DeferredDocumentProvider defers the document provision. A please-wait page is displayed until * loading has completed on a worker thread. * <p> * setDocumentContent is intercepted and the sourceText is queued for assignment by a DeferredSetTextJob * a waiting message is displayed instead. * <br> * once Editor.createPartControl() is complete the BaseEditor should invoke scheduleDeferredSetTextJob * to assign the true sourceText triggering what appears to be an edit with reconciliations, validations * and updates. */ public class DeferredDocumentProvider extends XtextDocumentProvider { /** * Job scheduled on a worker thread to compute the editor text. */ protected class DeferredSetTextJob extends Job { protected final @NonNull XtextDocument document; protected final @NonNull String sourceText; public DeferredSetTextJob(@NonNull XtextDocument document, @NonNull String sourceText) { super("Deferred Editor SetText"); this.document = document; this.sourceText = sourceText; // System.out.println("Create DeferredSetTextJob@" + System.identityHashCode(this)); } @Override protected IStatus run(final IProgressMonitor monitor) { // System.out.println("Schedule DeferredSetTextJob@" + System.identityHashCode(this)); Boolean didIt = document.modify(new DeferredSetTextUnitOfWork(document, sourceText)); if (didIt != Boolean.TRUE) { schedule(250); } return Status.OK_STATUS; } } /** * IUnitOfWork for execution on the worker thread with exclusive modify access to compute the * editor text. */ public class DeferredSetTextUnitOfWork implements IUnitOfWork<Boolean, XtextResource> { protected final @NonNull XtextDocument document; protected final @NonNull String sourceText; public DeferredSetTextUnitOfWork(@NonNull XtextDocument document, @NonNull String sourceText) { this.document = document; this.sourceText = sourceText; } @Override public Boolean exec(XtextResource state) throws Exception { if (state == null) { return Boolean.FALSE; } setDocumentText(document, sourceText); return Boolean.TRUE; } } /** * Runnable for execution on the main UI thread to actually assign the text. */ protected class DeferredSetTextRunnable implements Runnable { protected final @NonNull XtextDocument document; protected final @NonNull String displayText; public DeferredSetTextRunnable(@NonNull XtextDocument document, @NonNull String displayText) { this.displayText = displayText; this.document = document; } @Override public void run() { // System.out.println("SetText XtextDocument@" + System.identityHashCode(document) + "\n" + displayText); document.set(displayText); IEditorInput element = document2element.get(document); ElementInfo elementInfo = getElementInfo(element); if (elementInfo != null) { elementInfo.fCanBeSaved = false; } document2element.remove(document); /*DeferredSetTextJob oldJob =*/ document2job.remove(document); // System.out.println("Remove DeferredSetTextJob@" + System.identityHashCode(oldJob)); TextViewer sourceViewer = document2viewer.remove(document); fireElementDirtyStateChanged(element, false); if ((document instanceof BaseDocument) && (sourceViewer != null)) { IUndoManager undoManager = sourceViewer.getUndoManager(); if (undoManager != null) { undoManager.connect(sourceViewer); } } } } private @NonNull Map<IDocument, IEditorInput> document2element = new HashMap<IDocument, IEditorInput>(); private @NonNull Map<IDocument, IDocument> document2document = new HashMap<IDocument, IDocument>(); // See BUG 469967 private @NonNull Map<IDocument, DeferredSetTextJob> document2job = new HashMap<IDocument, DeferredSetTextJob>(); private @NonNull Map<IDocument, TextViewer> document2viewer = new HashMap<IDocument, TextViewer>(); protected @NonNull String getPleaseWaitText() { return "/* " + BaseUIMessages.DeferredDocumentProvider_PleaseWait + " */"; } @Override protected void handleElementContentChanged(IFileEditorInput editorInput) { super.handleElementContentChanged(editorInput); IDocument document = getDocument(editorInput); DeferredSetTextJob deferredLoadingJob = document2job.get(document); if (deferredLoadingJob != null) { // System.out.println("Schedule2 DeferredSetTextJob@" + System.identityHashCode(deferredLoadingJob)); // document2viewer.put(document, sourceViewer); deferredLoadingJob.schedule(100); } } public void scheduleDeferredSetTextJob(@NonNull BaseEditor baseEditor) { TextViewer sourceViewer = baseEditor.getTextViewer(); IEditorInput editorInput = baseEditor.getEditorInput(); IDocument document = getDocument(editorInput); DeferredSetTextJob deferredLoadingJob = document2job.get(document); if (deferredLoadingJob != null) { document2viewer.put(document, sourceViewer); deferredLoadingJob.schedule(100); // Give XtextReconciler, ValidationJob a chance to finish } } @Deprecated // not used from RC2 onwards public void scheduleDeferredSetTextJob(IEditorInput input) { IDocument document = getDocument(input); DeferredSetTextJob deferredLoadingJob = document2job.get(document); if (deferredLoadingJob != null) { deferredLoadingJob.schedule(100); // Give XtextReconciler, ValidationJob a chance to finish } } @Override protected boolean setDocumentContent(IDocument document, IEditorInput editorInput, String encoding) throws CoreException { document2element.put(document, editorInput); IDocument oldDocument = getDocument(editorInput); if (oldDocument != null) { document2document.put(document, oldDocument); } // System.out.println("setDocumentContent XtextDocument@" + System.identityHashCode(document) + " IEditorInput@" + System.identityHashCode(editorInput)); return super.setDocumentContent(document, editorInput, encoding); } @Override protected void setDocumentContent(IDocument document, InputStream contentStream, String encoding) throws CoreException { assert document instanceof XtextDocument; try { StringBuilder s = new StringBuilder(); Reader reader = new InputStreamReader(contentStream, encoding); char[] cbuf = new char[16384]; int len; while ((len = reader.read(cbuf)) > 0) { s.append(cbuf, 0, len); } @NonNull String string = s.toString(); IDocument document2 = document2document.get(document); if (document2 != null) { document = document2; } DeferredSetTextJob deferredLoadingJob = new DeferredSetTextJob((XtextDocument) document, string); document2job.put(document, deferredLoadingJob); String loading = getPleaseWaitText(); InputStream is = new ByteArrayInputStream(loading.getBytes()); // System.out.println("setDocumentContent XtextDocument@" + System.identityHashCode(document) + "\n" + loading); super.setDocumentContent(document, is, encoding); } catch (IOException e) { IStatus status = new Status(IStatus.ERROR, BaseUiPluginHelper.PLUGIN_ID, IStatus.OK, "Failed to read document", e); throw new CoreException(status); } } /** * Define the content of document as text. This is invoked by the Job and queues * an update on the main UI thread. It may be overloaded to change the text from sourceText * to the displayText. */ protected void setDocumentText(final @NonNull XtextDocument document, final @NonNull String text) throws CoreException { Runnable displayRefresh = new DeferredSetTextRunnable(document, text); Display.getDefault().asyncExec(displayRefresh); } }