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
Event unguarded_access: Accessing "acl2s.plugin.editors.MyQuickDiffRefProvider.fReference" without holding a lock on "acl2s.plugin.editors.MyQuickDiffRefProvider.fLock".
Also see events: [lock_event][guarded_by_access][lock_event][guarded_by_access]
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  	
Event lock_event: Locking "acl2s.plugin.editors.MyQuickDiffRefProvider.fLock".
Also see events: [unguarded_access][guarded_by_access][lock_event][guarded_by_access]
114  			synchronized (fLock) {
115  				if (provider != null)
116  					provider.removeElementStateListener(this);
117  				fEditorInput= null;
118  				fDocumentProvider= null;
Event guarded_by_access: Field "acl2s.plugin.editors.MyQuickDiffRefProvider.fReference" guarded by lock "acl2s.plugin.editors.MyQuickDiffRefProvider.fLock".
Also see events: [unguarded_access][lock_event][lock_event][guarded_by_access]
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
Event lock_event: Locking "acl2s.plugin.editors.MyQuickDiffRefProvider.fLock".
Also see events: [unguarded_access][lock_event][guarded_by_access][guarded_by_access]
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)
Event guarded_by_access: Field "acl2s.plugin.editors.MyQuickDiffRefProvider.fReference" guarded by lock "acl2s.plugin.editors.MyQuickDiffRefProvider.fLock".
Also see events: [unguarded_access][lock_event][guarded_by_access][lock_event]
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  	}