KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > internal > editors > quickdiff > LastSaveReferenceProvider


1 /*******************************************************************************
2  * Copyright (c) 2000, 2005 IBM Corporation and others.
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
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.ui.internal.editors.quickdiff;
12
13 import java.io.BufferedReader JavaDoc;
14 import java.io.IOException JavaDoc;
15 import java.io.InputStream JavaDoc;
16 import java.io.InputStreamReader JavaDoc;
17 import java.io.Reader JavaDoc;
18
19 import org.eclipse.core.resources.IEncodedStorage;
20 import org.eclipse.core.resources.IFile;
21 import org.eclipse.core.resources.IStorage;
22 import org.eclipse.core.runtime.CoreException;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.core.runtime.IStatus;
25 import org.eclipse.core.runtime.OperationCanceledException;
26 import org.eclipse.core.runtime.Platform;
27 import org.eclipse.core.runtime.Status;
28 import org.eclipse.core.runtime.content.IContentDescription;
29 import org.eclipse.core.runtime.jobs.IJobManager;
30 import org.eclipse.core.runtime.jobs.ISchedulingRule;
31 import org.eclipse.core.runtime.jobs.Job;
32
33 import org.eclipse.swt.widgets.Display;
34 import org.eclipse.swt.widgets.Shell;
35
36 import org.eclipse.jface.text.Document;
37 import org.eclipse.jface.text.IDocument;
38
39 import org.eclipse.ui.IEditorInput;
40 import org.eclipse.ui.IStorageEditorInput;
41 import org.eclipse.ui.editors.text.EditorsUI;
42 import org.eclipse.ui.editors.text.IStorageDocumentProvider;
43 import org.eclipse.ui.IWorkbenchPartSite;
44 import org.eclipse.ui.IWorkbenchWindow;
45 import org.eclipse.ui.texteditor.IDocumentProvider;
46 import org.eclipse.ui.texteditor.IElementStateListener;
47 import org.eclipse.ui.texteditor.ITextEditor;
48 import org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider;
49
50 /**
51  * Default provider for the quickdiff display - the saved document is taken as
52  * the reference.
53  *
54  * @since 3.0
55  */

56 public class LastSaveReferenceProvider implements IQuickDiffReferenceProvider, IElementStateListener {
57
58     /** <code>true</code> if the document has been read. */
59     private boolean fDocumentRead= false;
60     /**
61      * The reference document - might be <code>null</code> even if <code>fDocumentRead</code>
62      * is <code>true</code>.
63      */

64     private IDocument fReference= null;
65     /**
66      * Our unique id that makes us comparable to another instance of the same
67      * provider. See extension point reference.
68      */

69     private String JavaDoc fId;
70     /** The current document provider. */
71     private IDocumentProvider fDocumentProvider;
72     /** The current editor input. */
73     private IEditorInput fEditorInput;
74     /** Private lock no one else will synchronize on. */
75     private final Object JavaDoc fLock= new Object JavaDoc();
76     /** The document lock for non-IResources. */
77     private final Object JavaDoc fDocumentAccessorLock= new Object JavaDoc();
78     /** Document lock state, protected by <code>fDocumentAccessorLock</code>. */
79     private boolean fDocumentLocked;
80     /**
81      * The progress monitor for a currently running <code>getReference</code>
82      * operation, or <code>null</code>.
83      */

84     private IProgressMonitor fProgressMonitor;
85     /** The text editor we run upon. */
86     private ITextEditor fEditor;
87
88     /**
89      * A job to put the reading of file contents into a background.
90      */

91     private final class ReadJob extends Job {
92
93         /**
94          * Creates a new instance.
95          */

96         public ReadJob() {
97             super(QuickDiffMessages.getString("LastSaveReferenceProvider.LastSaveReferenceProvider.readJob.label")); //$NON-NLS-1$
98
setSystem(true);
99             setPriority(SHORT);
100         }
101
102         /**
103          * Calls
104          * {@link LastSaveReferenceProvider#readDocument(IProgressMonitor, boolean)}
105          * and returns {@link Status#OK_STATUS}.
106          *
107          * {@inheritDoc}
108          *
109          * @param monitor {@inheritDoc}
110          * @return {@link Status#OK_STATUS}
111          */

112         protected IStatus run(IProgressMonitor monitor) {
113             readDocument(monitor, false);
114             return Status.OK_STATUS;
115         }
116     }
117
118     /*
119      * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider#getReference(org.eclipse.core.runtime.IProgressMonitor)
120      */

121     public IDocument getReference(IProgressMonitor monitor) {
122         if (!fDocumentRead)
123             readDocument(monitor, true); // force reading it
124
return fReference;
125     }
126
127     /*
128      * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider#dispose()
129      */

130     public void dispose() {
131         IProgressMonitor monitor= fProgressMonitor;
132         if (monitor != null) {
133             monitor.setCanceled(true);
134         }
135
136         IDocumentProvider provider= fDocumentProvider;
137
138         synchronized (fLock) {
139             if (provider != null)
140                 provider.removeElementStateListener(this);
141             fEditorInput= null;
142             fDocumentProvider= null;
143             fReference= null;
144             fDocumentRead= false;
145             fProgressMonitor= null;
146             fEditor= null;
147         }
148     }
149
150     /*
151      * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffReferenceProvider#getId()
152      */

153     public String JavaDoc getId() {
154         return fId;
155     }
156
157     /*
158      * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffProviderImplementation#setActiveEditor(org.eclipse.ui.texteditor.ITextEditor)
159      */

160     public void setActiveEditor(ITextEditor targetEditor) {
161         IDocumentProvider provider= null;
162         IEditorInput input= null;
163         if (targetEditor != null) {
164             provider= targetEditor.getDocumentProvider();
165             input= targetEditor.getEditorInput();
166         }
167
168
169         // dispose if the editor input or document provider have changed
170
// note that they may serve multiple editors
171
if (provider != fDocumentProvider || input != fEditorInput) {
172             dispose();
173             synchronized (fLock) {
174                 fEditor= targetEditor;
175                 fDocumentProvider= provider;
176                 fEditorInput= input;
177             }
178         }
179     }
180
181     /*
182      * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffProviderImplementation#isEnabled()
183      */

184     public boolean isEnabled() {
185         return fEditorInput != null && fDocumentProvider != null;
186     }
187
188     /*
189      * @see org.eclipse.ui.texteditor.quickdiff.IQuickDiffProviderImplementation#setId(java.lang.String)
190      */

191     public void setId(String JavaDoc id) {
192         fId= id;
193     }
194
195     /**
196      * Reads in the saved document into <code>fReference</code>.
197      *
198      * @param monitor a progress monitor, or <code>null</code>
199      * @param force <code>true</code> if the reference document should also
200      * be read if the current document is <code>null</code>,<code>false</code>
201      * if it should only be updated if it already existed.
202      */

203     private void readDocument(IProgressMonitor monitor, boolean force) {
204
205         // protect against concurrent disposal
206
IDocumentProvider prov= fDocumentProvider;
207         IEditorInput inp= fEditorInput;
208         IDocument doc= fReference;
209         ITextEditor editor= fEditor;
210
211         if (prov instanceof IStorageDocumentProvider && inp instanceof IStorageEditorInput) {
212
213             IStorageEditorInput input= (IStorageEditorInput) inp;
214             IStorageDocumentProvider provider= (IStorageDocumentProvider) prov;
215
216             if (doc == null)
217                 if (force || fDocumentRead)
218                     doc= new Document();
219                 else
220                     return;
221
222             IJobManager jobMgr= Platform.getJobManager();
223
224             try {
225                 IStorage storage= input.getStorage();
226                 // check for null for backward compatibility (we used to check before...)
227
if (storage == null)
228                     return;
229                 fProgressMonitor= monitor;
230                 ISchedulingRule rule= getSchedulingRule(storage);
231
232                 // this protects others from not being able to delete the file,
233
// and protects ourselves from concurrent access to fReference
234
// (in the case there already is a valid fReference)
235

236                 // one might argue that this rule should already be in the Job
237
// description we're running in, however:
238
// 1) we don't mind waiting for someone else here
239
// 2) we do not take long, or require other locks etc. -> short
240
// delay for any other job requiring the lock on file
241
try {
242                     lockDocument(monitor, jobMgr, rule);
243
244                     String JavaDoc encoding;
245                     if (storage instanceof IEncodedStorage)
246                         encoding= ((IEncodedStorage) storage).getCharset();
247                     else
248                         encoding= null;
249
250                     boolean skipUTF8BOM= isUTF8BOM(encoding, storage);
251
252                     setDocumentContent(doc, storage, encoding, monitor, skipUTF8BOM);
253                 } finally {
254                     unlockDocument(jobMgr, rule);
255                     fProgressMonitor= null;
256                 }
257
258             } catch (CoreException e) {
259                 return;
260             }
261
262             if (monitor != null && monitor.isCanceled())
263                 return;
264
265             // update state
266
synchronized (fLock) {
267                 if (fDocumentProvider == provider && fEditorInput == input) {
268                     // only update state if our provider / input pair has not
269
// been updated in between (dispose or setActiveEditor)
270
fReference= doc;
271                     fDocumentRead= true;
272                     addElementStateListener(editor, prov);
273                 }
274             }
275         }
276     }
277
278     private ISchedulingRule getSchedulingRule(IStorage storage) {
279         if (storage instanceof ISchedulingRule)
280             return (ISchedulingRule) storage;
281         else if (storage != null)
282             return (ISchedulingRule) storage.getAdapter(ISchedulingRule.class);
283         return null;
284     }
285
286     /* utility methods */
287
288     private void lockDocument(IProgressMonitor monitor, IJobManager jobMgr, ISchedulingRule rule) {
289         if (rule != null) {
290             jobMgr.beginRule(rule, monitor);
291         } else synchronized (fDocumentAccessorLock) {
292             while (fDocumentLocked) {
293                 try {
294                     fDocumentAccessorLock.wait();
295                 } catch (InterruptedException JavaDoc e) {
296                     // nobody interrupts us!
297
throw new OperationCanceledException();
298                 }
299             }
300             fDocumentLocked= true;
301         }
302     }
303
304     private void unlockDocument(IJobManager jobMgr, ISchedulingRule rule) {
305         if (rule != null) {
306             jobMgr.endRule(rule);
307         } else synchronized (fDocumentAccessorLock) {
308             fDocumentLocked= false;
309             fDocumentAccessorLock.notifyAll();
310         }
311     }
312
313     /**
314      * Adds this as element state listener in the UI thread as it can otherwise
315      * conflict with other listener additions, since DocumentProvider is not
316      * thread-safe.
317      *
318      * @param editor the editor to get the display from
319      * @param provider the document provider to register as element state listener
320      */

321     private void addElementStateListener(ITextEditor editor, final IDocumentProvider provider) {
322         // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=66686 and
323
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=56871
324

325         Runnable JavaDoc runnable= new Runnable JavaDoc() {
326             public void run() {
327                 synchronized (fLock) {
328                     if (fDocumentProvider == provider)
329                         // addElementStateListener adds at most once - no problem to call repeatedly
330
provider.addElementStateListener(LastSaveReferenceProvider.this);
331                 }
332             }
333         };
334
335         Display display= null;
336         if (editor != null) {
337             IWorkbenchPartSite site= editor.getSite();
338             if (site != null) {
339                 IWorkbenchWindow window= site.getWorkbenchWindow();
340                 if (window != null) {
341                     Shell shell= window.getShell();
342                     if (shell != null)
343                         display= shell.getDisplay();
344                 }
345             }
346         }
347
348         if (display != null && !display.isDisposed())
349             display.asyncExec(runnable);
350         else
351             runnable.run();
352     }
353
354     /**
355      * Initializes the given document with the given stream using the given
356      * encoding.
357      *
358      * @param document the document to be initialized
359      * @param storage the storage which delivers the document content
360      * @param encoding the character encoding for reading the given stream
361      * @param monitor a progress monitor for cancellation, or <code>null</code>
362      * @param skipUTF8BOM whether to skip three bytes before reading the stream
363      * @exception CoreException if the given storage can not be accessed or read
364      */

365     private static void setDocumentContent(IDocument document, IStorage storage, String JavaDoc encoding, IProgressMonitor monitor, boolean skipUTF8BOM) throws CoreException {
366         Reader JavaDoc in= null;
367         InputStream JavaDoc contentStream= storage.getContents();
368         try {
369
370             if (skipUTF8BOM) {
371                 for (int i= 0; i < 3; i++)
372                     if (contentStream.read() == -1) {
373                         throw new IOException JavaDoc(QuickDiffMessages.getString("LastSaveReferenceProvider.LastSaveReferenceProvider.error.notEnoughBytesForBOM")); //$NON-NLS-1$
374
}
375             }
376
377             final int DEFAULT_FILE_SIZE= 15 * 1024;
378
379             if (encoding == null)
380                 in= new BufferedReader JavaDoc(new InputStreamReader JavaDoc(contentStream), DEFAULT_FILE_SIZE);
381             else
382                 in= new BufferedReader JavaDoc(new InputStreamReader JavaDoc(contentStream, encoding), DEFAULT_FILE_SIZE);
383             StringBuffer JavaDoc buffer= new StringBuffer JavaDoc(DEFAULT_FILE_SIZE);
384             char[] readBuffer= new char[2048];
385             int n= in.read(readBuffer);
386             while (n > 0) {
387                 if (monitor != null && monitor.isCanceled())
388                     return;
389
390                 buffer.append(readBuffer, 0, n);
391                 n= in.read(readBuffer);
392             }
393
394             document.set(buffer.toString());
395
396         } catch (IOException JavaDoc x) {
397             throw new CoreException(new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, "Failed to access or read underlying storage", x)); //$NON-NLS-1$
398
} finally {
399             try {
400                 if (in != null)
401                     in.close();
402                 else
403                     contentStream.close();
404             } catch (IOException JavaDoc x) {
405                 // ignore
406
}
407         }
408     }
409
410     /**
411      * Returns <code>true</code> if the <code>encoding</code> is UTF-8 and
412      * the file contains a BOM. Taken from ResourceTextFileBuffer.java.
413      *
414      * <p>
415      * XXX:
416      * This is a workaround for a corresponding bug in Java readers and writer,
417      * see: http://developer.java.sun.com/developer/bugParade/bugs/4508058.html
418      * </p>
419      * @param encoding the encoding
420      * @param storage the storage
421      * @return <code>true</code> if <code>storage</code> is a UTF-8-encoded file
422      * that has an UTF-8 BOM
423      * @throws CoreException if
424      * - reading of file's content description fails
425      * - byte order mark is not valid for UTF-8
426      */

427     private static boolean isUTF8BOM(String JavaDoc encoding, IStorage storage) throws CoreException {
428         if (storage instanceof IFile && "UTF-8".equals(encoding)) { //$NON-NLS-1$
429
IFile file= (IFile) storage;
430             IContentDescription description= file.getContentDescription();
431             if (description != null) {
432                 byte[] bom= (byte[]) description.getProperty(IContentDescription.BYTE_ORDER_MARK);
433                 if (bom != null) {
434                     if (bom != IContentDescription.BOM_UTF_8)
435                         throw new CoreException(new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, QuickDiffMessages.getString("LastSaveReferenceProvider.LastSaveReferenceProvider.error.wrongByteOrderMark"), null)); //$NON-NLS-1$
436
return true;
437                 }
438             }
439         }
440         return false;
441     }
442
443     /* IElementStateListener implementation */
444
445     /*
446      * @see org.eclipse.ui.texteditor.IElementStateListener#elementDirtyStateChanged(java.lang.Object, boolean)
447      */

448     public void elementDirtyStateChanged(Object JavaDoc element, boolean isDirty) {
449         if (!isDirty && element == fEditorInput) {
450             // document has been saved or reverted - recreate reference
451
new ReadJob().schedule();
452         }
453     }
454
455     /*
456      * @see org.eclipse.ui.texteditor.IElementStateListener#elementContentAboutToBeReplaced(java.lang.Object)
457      */

458     public void elementContentAboutToBeReplaced(Object JavaDoc element) {
459     }
460
461     /*
462      * @see org.eclipse.ui.texteditor.IElementStateListener#elementContentReplaced(java.lang.Object)
463      */

464     public void elementContentReplaced(Object JavaDoc element) {
465         if (element == fEditorInput) {
466             // document has been reverted or replaced
467
new ReadJob().schedule();
468         }
469     }
470
471     /*
472      * @see org.eclipse.ui.texteditor.IElementStateListener#elementDeleted(java.lang.Object)
473      */

474     public void elementDeleted(Object JavaDoc element) {
475     }
476
477     /*
478      * @see org.eclipse.ui.texteditor.IElementStateListener#elementMoved(java.lang.Object, java.lang.Object)
479      */

480     public void elementMoved(Object JavaDoc originalElement, Object JavaDoc movedElement) {
481     }
482 }
483
Popular Tags