KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > core > internal > filebuffers > ResourceTextFileBuffer


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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.core.internal.filebuffers;
12
13 import java.io.BufferedReader JavaDoc;
14 import java.io.ByteArrayInputStream JavaDoc;
15 import java.io.IOException JavaDoc;
16 import java.io.InputStream JavaDoc;
17 import java.io.InputStreamReader JavaDoc;
18 import java.io.Reader JavaDoc;
19 import java.io.SequenceInputStream JavaDoc;
20 import java.nio.ByteBuffer JavaDoc;
21 import java.nio.CharBuffer JavaDoc;
22 import java.nio.charset.CharacterCodingException JavaDoc;
23 import java.nio.charset.Charset JavaDoc;
24 import java.nio.charset.CharsetEncoder JavaDoc;
25 import java.nio.charset.CodingErrorAction JavaDoc;
26 import java.nio.charset.IllegalCharsetNameException JavaDoc;
27 import java.nio.charset.UnmappableCharacterException JavaDoc;
28 import java.nio.charset.UnsupportedCharsetException JavaDoc;
29
30 import org.eclipse.core.runtime.Assert;
31 import org.eclipse.core.runtime.CoreException;
32 import org.eclipse.core.runtime.IProgressMonitor;
33 import org.eclipse.core.runtime.IStatus;
34 import org.eclipse.core.runtime.Platform;
35 import org.eclipse.core.runtime.QualifiedName;
36 import org.eclipse.core.runtime.Status;
37 import org.eclipse.core.runtime.SubProgressMonitor;
38 import org.eclipse.core.runtime.content.IContentDescription;
39 import org.eclipse.core.runtime.content.IContentType;
40
41 import org.eclipse.core.resources.IFile;
42
43 import org.eclipse.core.filebuffers.IFileBufferStatusCodes;
44 import org.eclipse.core.filebuffers.IPersistableAnnotationModel;
45 import org.eclipse.core.filebuffers.ITextFileBuffer;
46 import org.eclipse.core.filebuffers.manipulation.ContainerCreator;
47
48 import org.eclipse.jface.text.DocumentEvent;
49 import org.eclipse.jface.text.IDocument;
50 import org.eclipse.jface.text.IDocumentExtension4;
51 import org.eclipse.jface.text.IDocumentListener;
52 import org.eclipse.jface.text.source.IAnnotationModel;
53
54 /**
55  * @since 3.0
56  */

57 public class ResourceTextFileBuffer extends ResourceFileBuffer implements ITextFileBuffer {
58
59     private class DocumentListener implements IDocumentListener {
60
61         /*
62          * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
63          */

64         public void documentAboutToBeChanged(DocumentEvent event) {
65         }
66
67         /*
68          * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
69          */

70         public void documentChanged(DocumentEvent event) {
71             if (fCanBeSaved && fSynchronizationStamp == event.getModificationStamp()) {
72                 fCanBeSaved= false;
73                 fManager.fireDirtyStateChanged(ResourceTextFileBuffer.this, fCanBeSaved);
74             } else if (!fCanBeSaved) {
75                 fCanBeSaved= true;
76                 fManager.fireDirtyStateChanged(ResourceTextFileBuffer.this, fCanBeSaved);
77             }
78         }
79     }
80
81     /**
82      * Reader chunk size.
83      */

84     static final private int READER_CHUNK_SIZE= 2048;
85     /**
86      * Buffer size.
87      */

88     static final private int BUFFER_SIZE= 8 * READER_CHUNK_SIZE;
89     /**
90      * Qualified name for the encoding key.
91      */

92     static final private QualifiedName ENCODING_KEY= new QualifiedName(FileBuffersPlugin.PLUGIN_ID, "encoding"); //$NON-NLS-1$
93
/**
94      * Constant for representing the OK status. This is considered a value object.
95      */

96     static final private IStatus STATUS_OK= new Status(IStatus.OK, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, FileBuffersMessages.FileBuffer_status_ok, null);
97     /**
98      * Constant for representing the error status. This is considered a value object.
99      */

100     static final private IStatus STATUS_ERROR= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, FileBuffersMessages.FileBuffer_status_error, null);
101     /**
102      * Constant denoting UTF-8 encoding.
103      */

104     private static final String JavaDoc CHARSET_UTF_8= "UTF-8"; //$NON-NLS-1$
105

106     /**
107      * Constant denoting an empty set of properties
108      * @since 3.1
109      */

110     private static final QualifiedName[] NO_PROPERTIES= new QualifiedName[0];
111
112
113     /** The element's document */
114     protected IDocument fDocument;
115     /** The encoding used to create the document from the storage or <code>null</code> for workbench encoding. */
116     protected String JavaDoc fEncoding;
117     /** Internal document listener */
118     protected IDocumentListener fDocumentListener= new DocumentListener();
119     /** The element's annotation model */
120     protected IAnnotationModel fAnnotationModel;
121     /** The encoding which has explicitly been set on the file. */
122     private String JavaDoc fExplicitEncoding;
123     /** Tells whether the file on disk has a BOM. */
124     private boolean fHasBOM;
125     /**
126      * Lock for lazy creation of annotation model.
127      * @since 3.2
128      */

129     private final Object JavaDoc fAnnotationModelCreationLock= new Object JavaDoc();
130
131
132     public ResourceTextFileBuffer(ResourceTextFileBufferManager manager) {
133         super(manager);
134     }
135
136     /*
137      * @see org.eclipse.core.filebuffers.ITextFileBuffer#getDocument()
138      */

139     public IDocument getDocument() {
140         return fDocument;
141     }
142
143     /*
144      * @see org.eclipse.core.filebuffers.ITextFileBuffer#getAnnotationModel()
145      */

146     public IAnnotationModel getAnnotationModel() {
147         synchronized (fAnnotationModelCreationLock) {
148             if (fAnnotationModel == null && !isDisconnected()) {
149                 fAnnotationModel= fManager.createAnnotationModel(fFile);
150                 if (fAnnotationModel != null)
151                     fAnnotationModel.connect(fDocument);
152             }
153         }
154         return fAnnotationModel;
155     }
156
157     /*
158      * @see org.eclipse.core.filebuffers.ITextFileBuffer#getEncoding()
159      */

160     public String JavaDoc getEncoding() {
161         return fEncoding;
162     }
163
164     /*
165      * @see org.eclipse.core.filebuffers.ITextFileBuffer#setEncoding(java.lang.String)
166      */

167     public void setEncoding(String JavaDoc encoding) {
168         fEncoding= encoding;
169         fExplicitEncoding= encoding;
170         fHasBOM= false;
171         try {
172             fFile.setCharset(encoding, null);
173             if (encoding == null)
174                 fEncoding= fFile.getCharset();
175             setHasBOM();
176         } catch (CoreException x) {
177             handleCoreException(x);
178         }
179     }
180
181     /*
182      * @see org.eclipse.core.filebuffers.IFileBuffer#getStatus()
183      */

184     public IStatus getStatus() {
185         if (!isDisconnected()) {
186             if (fStatus != null)
187                 return fStatus;
188             return (fDocument == null ? STATUS_ERROR : STATUS_OK);
189         }
190         return STATUS_ERROR;
191     }
192
193     /*
194      * @see org.eclipse.core.filebuffers.IFileBuffer#getContentType()
195      * @since 3.1
196      */

197     public IContentType getContentType() throws CoreException {
198         try {
199             if (isDirty()) {
200                 Reader JavaDoc reader= null;
201                 try {
202                     reader= new DocumentReader(getDocument());
203                     IContentDescription desc= Platform.getContentTypeManager().getDescriptionFor(reader, fFile.getName(), NO_PROPERTIES);
204                     if (desc != null && desc.getContentType() != null)
205                         return desc.getContentType();
206                 } finally {
207                     try {
208                         if (reader != null)
209                             reader.close();
210                     } catch (IOException JavaDoc x) {
211                     }
212                 }
213             }
214             IContentDescription desc= fFile.getContentDescription();
215             if (desc != null && desc.getContentType() != null)
216                 return desc.getContentType();
217             return null;
218         } catch (IOException JavaDoc x) {
219             throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, NLSUtility.format(FileBuffersMessages.FileBuffer_error_queryContentDescription, fFile.getFullPath().toOSString()), x));
220         }
221     }
222
223     /*
224      * @see org.eclipse.core.internal.filebuffers.FileBuffer#addFileBufferContentListeners()
225      */

226     protected void addFileBufferContentListeners() {
227         if (fDocument != null)
228             fDocument.addDocumentListener(fDocumentListener);
229     }
230
231     /*
232      * @see org.eclipse.core.internal.filebuffers.FileBuffer#removeFileBufferContentListeners()
233      */

234     protected void removeFileBufferContentListeners() {
235         if (fDocument != null)
236             fDocument.removeDocumentListener(fDocumentListener);
237     }
238
239     /*
240      * @see org.eclipse.core.internal.filebuffers.FileBuffer#initializeFileBufferContent(org.eclipse.core.runtime.IProgressMonitor)
241      */

242     protected void initializeFileBufferContent(IProgressMonitor monitor) throws CoreException {
243         try {
244             fEncoding= null;
245             fExplicitEncoding= null;
246             try {
247                 fEncoding= fFile.getPersistentProperty(ENCODING_KEY);
248             } catch (CoreException x) {
249                 // we ignore exceptions here because we support the ENCODING_KEY property only for compatibility reasons
250
}
251             if (fEncoding != null) {
252                 // if we found an old encoding property, we try to migrate it to the new core.resources encoding support
253
try {
254                     fExplicitEncoding= fEncoding;
255                     fFile.setCharset(fEncoding, monitor);
256                     // if successful delete old property
257
fFile.setPersistentProperty(ENCODING_KEY, null);
258                 } catch (CoreException ex) {
259                     // log problem because we could not migrate the property successfully
260
handleCoreException(ex);
261                 }
262                 setHasBOM();
263             } else {
264                 cacheEncodingState();
265             }
266
267
268             fDocument= fManager.createEmptyDocument(fFile);
269             setDocumentContent(fDocument, fFile, fEncoding);
270
271         } catch (CoreException x) {
272             fDocument= fManager.createEmptyDocument(fFile);
273             fStatus= x.getStatus();
274         }
275     }
276
277     /**
278      * Sets whether the underlying file has a BOM.
279      *
280      * @throws CoreException if reading of file's content description fails
281      */

282     protected void setHasBOM() throws CoreException {
283         fHasBOM= false;
284         IContentDescription description= fFile.getContentDescription();
285         fHasBOM= description != null && description.getProperty(IContentDescription.BYTE_ORDER_MARK) != null;
286     }
287
288     /*
289      * @see org.eclipse.core.internal.filebuffers.ResourceFileBuffer#connected()
290      */

291     protected void connected() {
292         super.connected();
293         if (fAnnotationModel != null)
294             fAnnotationModel.connect(fDocument);
295     }
296
297     /*
298      * @see org.eclipse.core.internal.filebuffers.ResourceFileBuffer#disconnected()
299      */

300     protected void dispose() {
301         if (fAnnotationModel != null)
302             fAnnotationModel.disconnect(fDocument);
303         fDocument= null;
304         super.dispose();
305     }
306
307     /*
308      * @see org.eclipse.core.internal.filebuffers.FileBuffer#commitFileBufferContent(org.eclipse.core.runtime.IProgressMonitor, boolean)
309      */

310     protected void commitFileBufferContent(IProgressMonitor monitor, boolean overwrite) throws CoreException {
311         String JavaDoc encoding= computeEncoding();
312
313         Charset JavaDoc charset;
314         try {
315             charset= Charset.forName(encoding);
316         } catch (UnsupportedCharsetException JavaDoc ex) {
317             String JavaDoc message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_unsupported_encoding_message_arg, encoding);
318             IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, ex);
319             throw new CoreException(s);
320         } catch (IllegalCharsetNameException JavaDoc ex) {
321             String JavaDoc message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_illegal_encoding_message_arg, encoding);
322             IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, ex);
323             throw new CoreException(s);
324         }
325
326         CharsetEncoder JavaDoc encoder= charset.newEncoder();
327         encoder.onMalformedInput(CodingErrorAction.REPLACE);
328         encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
329         
330         InputStream JavaDoc stream;
331         
332         try {
333             byte[] bytes;
334             ByteBuffer JavaDoc byteBuffer= encoder.encode(CharBuffer.wrap(fDocument.get()));
335             if (byteBuffer.hasArray())
336                 bytes= byteBuffer.array();
337             else {
338                 bytes= new byte[byteBuffer.limit()];
339                 byteBuffer.get(bytes);
340             }
341             stream= new ByteArrayInputStream JavaDoc(bytes, 0, byteBuffer.limit());
342         } catch (CharacterCodingException JavaDoc ex) {
343             Assert.isTrue(ex instanceof UnmappableCharacterException JavaDoc);
344             String JavaDoc message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_charset_mapping_failed_message_arg, encoding);
345             IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CHARSET_MAPPING_FAILED, message, null);
346             throw new CoreException(s);
347         }
348
349         /*
350          * XXX:
351          * This is a workaround for a corresponding bug in Java readers and writer,
352          * see: http://developer.java.sun.com/developer/bugParade/bugs/4508058.html
353          */

354         if (fHasBOM && CHARSET_UTF_8.equals(encoding))
355             stream= new SequenceInputStream JavaDoc(new ByteArrayInputStream JavaDoc(IContentDescription.BOM_UTF_8), stream);
356
357         if (fFile.exists()) {
358
359             // here the file synchronizer should actually be removed and afterwards added again. However,
360
// we are already inside an operation, so the delta is sent AFTER we have added the listener
361
fFile.setContents(stream, overwrite, true, monitor);
362             // set synchronization stamp to know whether the file synchronizer must become active
363

364             if (fDocument instanceof IDocumentExtension4) {
365                 fSynchronizationStamp= ((IDocumentExtension4)fDocument).getModificationStamp();
366                 fFile.revertModificationStamp(fSynchronizationStamp);
367             } else
368                 fSynchronizationStamp= fFile.getModificationStamp();
369
370             if (fAnnotationModel instanceof IPersistableAnnotationModel) {
371                 IPersistableAnnotationModel persistableModel= (IPersistableAnnotationModel) fAnnotationModel;
372                 persistableModel.commit(fDocument);
373             }
374
375         } else {
376
377             monitor= Progress.getMonitor(monitor);
378             try {
379                 monitor.beginTask(FileBuffersMessages.ResourceTextFileBuffer_task_saving, 2);
380                 ContainerCreator creator = new ContainerCreator(fFile.getWorkspace(), fFile.getParent().getFullPath());
381                 IProgressMonitor subMonitor= new SubProgressMonitor(monitor, 1);
382                 creator.createContainer(subMonitor);
383                 subMonitor.done();
384
385                 subMonitor= new SubProgressMonitor(monitor, 1);
386                 fFile.create(stream, false, subMonitor);
387                 subMonitor.done();
388
389             } finally {
390                 monitor.done();
391             }
392
393             // set synchronization stamp to know whether the file synchronizer must become active
394
fSynchronizationStamp= fFile.getModificationStamp();
395
396             // TODO commit persistable annotation model
397
}
398
399     }
400
401     private String JavaDoc computeEncoding() {
402         // User-defined encoding has first priority
403
if (fExplicitEncoding != null)
404             return fExplicitEncoding;
405
406         // Probe content
407
Reader JavaDoc reader= new DocumentReader(fDocument);
408         try {
409             QualifiedName[] options= new QualifiedName[] { IContentDescription.CHARSET, IContentDescription.BYTE_ORDER_MARK };
410             IContentDescription description= Platform.getContentTypeManager().getDescriptionFor(reader, fFile.getName(), options);
411             if (description != null) {
412                 String JavaDoc encoding= description.getCharset();
413                 if (encoding != null)
414                     return encoding;
415             }
416         } catch (IOException JavaDoc ex) {
417             // try next strategy
418
} finally {
419             try {
420                 if (reader != null)
421                     reader.close();
422             } catch (IOException JavaDoc x) {
423             }
424         }
425
426         // Use file's encoding if the file has a BOM
427
if (fHasBOM)
428             return fEncoding;
429
430         // Use parent chain
431
try {
432             return fFile.getParent().getDefaultCharset();
433         } catch (CoreException ex) {
434             // Use global default
435
return fManager.getDefaultEncoding();
436         }
437     }
438
439     /**
440      * Internally caches the text resource's encoding.
441      *
442      * @throws CoreException if the encoding cannot be retrieved from the resource
443      * @since 3.1
444      */

445     protected void cacheEncodingState() throws CoreException {
446         fExplicitEncoding= fFile.getCharset(false);
447         if (fExplicitEncoding != null)
448             fEncoding= fExplicitEncoding;
449         else
450             fEncoding= fFile.getCharset();
451         setHasBOM();
452     }
453
454     /*
455      * @see org.eclipse.core.internal.filebuffers.ResourceFileBuffer#handleFileContentChanged()
456      */

457     protected void handleFileContentChanged(boolean revert) throws CoreException {
458
459         IDocument document= fManager.createEmptyDocument(fFile);
460         IStatus status= null;
461
462         try {
463             cacheEncodingState();
464             setDocumentContent(document, fFile, fEncoding);
465         } catch (CoreException x) {
466             status= x.getStatus();
467         }
468
469         String JavaDoc newContent= document.get();
470         boolean replaceContent= !newContent.equals(fDocument.get());
471
472         if (replaceContent)
473             fManager.fireBufferContentAboutToBeReplaced(this);
474
475         removeFileBufferContentListeners();
476         fSynchronizationStamp= fFile.getModificationStamp();
477         if (replaceContent) {
478             if (fDocument instanceof IDocumentExtension4)
479                 ((IDocumentExtension4)fDocument).set(newContent, fSynchronizationStamp);
480             else
481                 fDocument.set(newContent);
482         }
483         fCanBeSaved= false;
484         fStatus= status;
485         addFileBufferContentListeners();
486
487         if (replaceContent)
488             fManager.fireBufferContentReplaced(this);
489
490         if (fAnnotationModel instanceof IPersistableAnnotationModel) {
491             IPersistableAnnotationModel persistableModel= (IPersistableAnnotationModel) fAnnotationModel;
492             try {
493                 if (revert)
494                     persistableModel.revert(fDocument);
495                 else
496                     persistableModel.reinitialize(fDocument);
497             } catch (CoreException x) {
498                 fStatus= x.getStatus();
499             }
500         }
501
502         fManager.fireDirtyStateChanged(this, fCanBeSaved);
503     }
504
505     /**
506      * Initializes the given document with the given stream using the given encoding.
507      *
508      * @param document the document to be initialized
509      * @param file the file which delivers the document content
510      * @param encoding the character encoding for reading the given stream
511      * @exception CoreException if the given stream can not be read
512      */

513     private void setDocumentContent(IDocument document, IFile file, String JavaDoc encoding) throws CoreException {
514         InputStream JavaDoc contentStream= file.getContents();
515         Reader JavaDoc in= null;
516         try {
517
518             if (encoding == null)
519                 encoding= fManager.getDefaultEncoding();
520
521             /*
522              * XXX:
523              * This is a workaround for a corresponding bug in Java readers and writer,
524              * see: http://developer.java.sun.com/developer/bugParade/bugs/4508058.html
525              */

526             if (fHasBOM && CHARSET_UTF_8.equals(encoding)) {
527                 int n= 0;
528                 do {
529                     int bytes= contentStream.read(new byte[IContentDescription.BOM_UTF_8.length]);
530                     if (bytes == -1)
531                         throw new IOException JavaDoc();
532                     n += bytes;
533                 } while (n < IContentDescription.BOM_UTF_8.length);
534             }
535
536             in= new BufferedReader JavaDoc(new InputStreamReader JavaDoc(contentStream, encoding), BUFFER_SIZE);
537             StringBuffer JavaDoc buffer= new StringBuffer JavaDoc(BUFFER_SIZE);
538             char[] readBuffer= new char[READER_CHUNK_SIZE];
539             int n= in.read(readBuffer);
540             while (n > 0) {
541                 buffer.append(readBuffer, 0, n);
542                 n= in.read(readBuffer);
543             }
544
545             if (document instanceof IDocumentExtension4)
546                 ((IDocumentExtension4)document).set(buffer.toString(), fFile.getModificationStamp());
547             else
548                 document.set(buffer.toString());
549
550         } catch (IOException JavaDoc x) {
551             String JavaDoc message= (x.getMessage() != null ? x.getMessage() : ""); //$NON-NLS-1$
552
IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, x);
553             throw new CoreException(s);
554         } finally {
555             try {
556                 if (in != null)
557                     in.close();
558                 else
559                     contentStream.close();
560             } catch (IOException JavaDoc x) {
561             }
562         }
563     }
564 }
565
Popular Tags