1 package acl2s.plugin.editors;
2
3 import java.util.MissingResourceException;
4 import java.util.ResourceBundle;
5
6 import org.eclipse.core.resources.IEncodedStorage;
7 import org.eclipse.core.resources.IStorage;
8 import org.eclipse.core.runtime.CoreException;
9 import org.eclipse.core.runtime.IProgressMonitor;
10 import org.eclipse.core.runtime.IStatus;
11 import org.eclipse.core.runtime.OperationCanceledException;
12 import org.eclipse.core.runtime.Status;
13 import org.eclipse.core.runtime.jobs.IJobManager;
14 import org.eclipse.core.runtime.jobs.ISchedulingRule;
15 import org.eclipse.core.runtime.jobs.Job;
16 import org.eclipse.jface.text.IDocument;
17 import org.eclipse.swt.widgets.Display;
18 import org.eclipse.swt.widgets.Shell;
19 import org.eclipse.ui.IEditorInput;
20 import org.eclipse.ui.IStorageEditorInput;
21 import org.eclipse.ui.IWorkbenchPartSite;
22 import org.eclipse.ui.IWorkbenchWindow;
23 import org.eclipse.ui.texteditor.IDocumentProvider;
24 import org.eclipse.ui.texteditor.IElementStateListener;
25 import org.eclipse.ui.texteditor.ITextEditor;
26 import org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider;
27
28 /**
29 * Copied from org.eclipse.ui.internal.editors.quickdiff.LastSaveReferenceProvider
30 */
31 public class MyQuickDiffRefProvider implements IQuickDiffReferenceProvider, IElementStateListener {
32 public static String id = "acl2s.plugin.editors.MyQuickDiffRefProvider";
33
34 /** <code>true</code> if the document has been read. */
35 private boolean fDocumentRead= false;
36 /**
37 * The reference document - might be <code>null</code> even if <code>fDocumentRead</code>
38 * is <code>true</code>.
39 */
40 private IDocument fReference= null;
41 /**
42 * Our unique id that makes us comparable to another instance of the same
43 * provider. See extension point reference.
44 */
45 private String fId;
46 /** The current document provider. */
47 private IDocumentProvider fDocumentProvider;
48 /** The current editor input. */
49 private IEditorInput fEditorInput;
50 /** Private lock no one else will synchronize on. */
51 private final Object fLock= new Object();
52 /** The document lock for non-IResources. */
53 private final Object fDocumentAccessorLock= new Object();
54 /** Document lock state, protected by <code>fDocumentAccessorLock</code>. */
55 private boolean fDocumentLocked;
56 /**
57 * The progress monitor for a currently running <code>getReference</code>
58 * operation, or <code>null</code>.
59 */
60 private IProgressMonitor fProgressMonitor;
61 /** The text editor we run upon. */
62 private ITextEditor fEditor;
63
64 /**
65 * A job to put the reading of file contents into a background.
66 */
67 private final class ReadJob extends Job {
68
69 /**
70 * Creates a new instance.
71 */
72 public ReadJob() {
73 super(getString("LastSaveReferenceProvider.LastSaveReferenceProvider.readJob.label")); //$NON-NLS-1$
74 setSystem(true);
75 setPriority(SHORT);
76 }
77
78 /**
79 * Calls
80 * {@link MyQuickDiffRefProvider#readDocument(IProgressMonitor, boolean)}
81 * and returns {@link Status#OK_STATUS}.
82 *
83 * {@inheritDoc}
84 *
85 * @param monitor {@inheritDoc}
86 * @return {@link Status#OK_STATUS}
87 */
88 protected IStatus run(IProgressMonitor monitor) {
89 readDocument(monitor, false);
90 return Status.OK_STATUS;
91 }
92 }
93
94 /*
95 * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider#getReference(org.eclipse.core.runtime.IProgressMonitor)
96 */
97 public IDocument getReference(IProgressMonitor monitor) {
98 if (!fDocumentRead)
99 readDocument(monitor, true); // force reading it
100 return fReference;
101 }
102
103 /*
104 * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider#dispose()
105 */
106 public void dispose() {
107 IProgressMonitor monitor= fProgressMonitor;
108 if (monitor != null) {
109 monitor.setCanceled(true);
110 }
111
112 IDocumentProvider provider= fDocumentProvider;
113
114 synchronized (fLock) {
115 if (provider != null)
116 provider.removeElementStateListener(this);
117 fEditorInput= null;
118 fDocumentProvider= null;
119 fReference= null;
120 fDocumentRead= false;
121 fProgressMonitor= null;
122 fEditor= null;
123 }
124 }
125
126 /*
127 * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider#getId()
128 */
129 public String getId() {
130 return fId;
131 }
132
133 /*
134 * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffProviderImplementation#setActiveEditor(org.eclipse.ui.texteditor.ITextEditor)
135 */
136 public void setActiveEditor(ITextEditor targetEditor) {
137 IDocumentProvider provider= null;
138 IEditorInput input= null;
139 if (targetEditor != null) {
140 provider= targetEditor.getDocumentProvider();
141 input= targetEditor.getEditorInput();
142 }
143
144
145 // dispose if the editor input or document provider have changed
146 // note that they may serve multiple editors
147 if (provider != fDocumentProvider || input != fEditorInput) {
148 dispose();
149 synchronized (fLock) {
150 fEditor= targetEditor;
151 fDocumentProvider= provider;
152 fEditorInput= input;
153 }
154 }
155 }
156
157 /*
158 * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffProviderImplementation#isEnabled()
159 */
160 public boolean isEnabled() {
161 return fEditorInput != null && fDocumentProvider != null;
162 }
163
164 /*
165 * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffProviderImplementation#setId(java.lang.String)
166 */
167 public void setId(String id) {
168 fId= id;
169 }
170
171 /**
172 * Reads in the saved document into <code>fReference</code>.
173 *
174 * @param monitor a progress monitor, or <code>null</code>
175 * @param force <code>true</code> if the reference document should also
176 * be read if the current document is <code>null</code>,<code>false</code>
177 * if it should only be updated if it already existed.
178 */
179 private void readDocument(IProgressMonitor monitor, boolean force) {
180
181 // protect against concurrent disposal
182 IDocumentProvider prov= fDocumentProvider;
183 IEditorInput inp= fEditorInput;
184 IDocument doc= fReference;
185 ITextEditor editor= fEditor;
186
187 if (prov instanceof MyDocumentProvider && inp instanceof IStorageEditorInput) {
188
189 IStorageEditorInput input= (IStorageEditorInput) inp;
190 MyDocumentProvider provider= (MyDocumentProvider) prov;
191
192 if (doc == null)
193 if (force || fDocumentRead)
194 doc = provider.createEmptyDocument();
195 else
196 return;
197
198 IJobManager jobMgr= Job.getJobManager();
199
200 try {
201 IStorage storage= input.getStorage();
202 // check for null for backward compatibility (we used to check before...)
203 if (storage == null)
204 return;
205 fProgressMonitor= monitor;
206 ISchedulingRule rule= getSchedulingRule(storage);
207
208 // this protects others from not being able to delete the file,
209 // and protects ourselves from concurrent access to fReference
210 // (in the case there already is a valid fReference)
211
212 // one might argue that this rule should already be in the Job
213 // description we're running in, however:
214 // 1) we don't mind waiting for someone else here
215 // 2) we do not take long, or require other locks etc. -> short
216 // delay for any other job requiring the lock on file
217 try {
218 lockDocument(monitor, jobMgr, rule);
219
220 String encoding;
221 if (storage instanceof IEncodedStorage)
222 encoding= ((IEncodedStorage) storage).getCharset();
223 else
224 encoding= null;
225
226 provider.setDocumentContent(doc, input, encoding);
227 } finally {
228 unlockDocument(jobMgr, rule);
229 fProgressMonitor= null;
230 }
231
232 } catch (CoreException e) {
233 return;
234 }
235
236 if (monitor != null && monitor.isCanceled())
237 return;
238
239 // update state
240 synchronized (fLock) {
241 if (fDocumentProvider == provider && fEditorInput == input) {
242 // only update state if our provider / input pair has not
243 // been updated in between (dispose or setActiveEditor)
244 fReference= doc;
245 fDocumentRead= true;
246 addElementStateListener(editor, prov);
247 }
248 }
249 }
250 }
251
252 private ISchedulingRule getSchedulingRule(IStorage storage) {
253 if (storage instanceof ISchedulingRule)
254 return (ISchedulingRule) storage;
255 else if (storage != null)
256 return (ISchedulingRule) storage.getAdapter(ISchedulingRule.class);
257 return null;
258 }
259
260 /* utility methods */
261
262 private void lockDocument(IProgressMonitor monitor, IJobManager jobMgr, ISchedulingRule rule) {
263 if (rule != null) {
264 jobMgr.beginRule(rule, monitor);
265 } else synchronized (fDocumentAccessorLock) {
266 while (fDocumentLocked) {
267 try {
268 fDocumentAccessorLock.wait();
269 } catch (InterruptedException e) {
270 // nobody interrupts us!
271 throw new OperationCanceledException();
272 }
273 }
274 fDocumentLocked= true;
275 }
276 }
277
278 private void unlockDocument(IJobManager jobMgr, ISchedulingRule rule) {
279 if (rule != null) {
280 jobMgr.endRule(rule);
281 } else synchronized (fDocumentAccessorLock) {
282 fDocumentLocked= false;
283 fDocumentAccessorLock.notifyAll();
284 }
285 }
286
287 /**
288 * Adds this as element state listener in the UI thread as it can otherwise
289 * conflict with other listener additions, since DocumentProvider is not
290 * thread-safe.
291 *
292 * @param editor the editor to get the display from
293 * @param provider the document provider to register as element state listener
294 */
295 private void addElementStateListener(ITextEditor editor, final IDocumentProvider provider) {
296 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=66686 and
297 // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=56871
298
299 Runnable runnable= new Runnable() {
300 public void run() {
301 synchronized (fLock) {
302 if (fDocumentProvider == provider)
303 // addElementStateListener adds at most once - no problem to call repeatedly
304 provider.addElementStateListener(MyQuickDiffRefProvider.this);
305 }
306 }
307 };
308
309 Display display= null;
310 if (editor != null) {
311 IWorkbenchPartSite site= editor.getSite();
312 if (site != null) {
313 IWorkbenchWindow window= site.getWorkbenchWindow();
314 if (window != null) {
315 Shell shell= window.getShell();
316 if (shell != null)
317 display= shell.getDisplay();
318 }
319 }
320 }
321
322 if (display != null && !display.isDisposed())
323 display.asyncExec(runnable);
324 else
325 runnable.run();
326 }
327
328
329 /* IElementStateListener implementation */
330
331 /*
332 * @see org.eclipse.ui.texteditor.IElementStateListener#elementDirtyStateChanged(java.lang.Object, boolean)
333 */
334 public void elementDirtyStateChanged(Object element, boolean isDirty) {
335 if (!isDirty && element == fEditorInput) {
336 // document has been saved or reverted - recreate reference
337 new ReadJob().schedule();
338 }
339 }
340
341 /*
342 * @see org.eclipse.ui.texteditor.IElementStateListener#elementContentAboutToBeReplaced(java.lang.Object)
343 */
344 public void elementContentAboutToBeReplaced(Object element) {
345 }
346
347 /*
348 * @see org.eclipse.ui.texteditor.IElementStateListener#elementContentReplaced(java.lang.Object)
349 */
350 public void elementContentReplaced(Object element) {
351 if (element == fEditorInput) {
352 // document has been reverted or replaced
353 new ReadJob().schedule();
354 }
355 }
356
357 /*
358 * @see org.eclipse.ui.texteditor.IElementStateListener#elementDeleted(java.lang.Object)
359 */
360 public void elementDeleted(Object element) {
361 }
362
363 /*
364 * @see org.eclipse.ui.texteditor.IElementStateListener#elementMoved(java.lang.Object, java.lang.Object)
365 */
366 public void elementMoved(Object originalElement, Object movedElement) {
367 }
368
369
370
371
372 // MESSAGES
373
374 private static final String BUNDLE_NAME= "org.eclipse.ui.internal.editors.quickdiff.QuickDiffMessages";//$NON-NLS-1$
375 private static final ResourceBundle RESOURCE_BUNDLE= ResourceBundle.getBundle(BUNDLE_NAME);
376
377 private static String getString(String key) {
378 try {
379 return RESOURCE_BUNDLE.getString(key);
380 } catch (MissingResourceException e) {
381 return '!' + key + '!';
382 }
383 }
384 /*
385 private static ResourceBundle getResourceBundle() {
386 return RESOURCE_BUNDLE;
387 }
388
389 private static String getFormattedString(String key, Object arg) {
390 return getFormattedString(key, new Object[] { arg });
391 }
392
393 private static String getFormattedString(String key, Object[] args) {
394 return MessageFormat.format(getString(key), args);
395 }
396 //*/
397 }