Copyright (c) 2000, 2014 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation
/******************************************************************************* * Copyright (c) 2000, 2014 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/
package org.eclipse.core.internal.filebuffers; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.SequenceInputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.StandardCharsets; import java.nio.charset.UnmappableCharacterException; import java.nio.charset.UnsupportedCharsetException; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.content.IContentDescription; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.filebuffers.IFileBufferStatusCodes; import org.eclipse.core.filebuffers.IPersistableAnnotationModel; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.manipulation.ContainerCreator; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.source.IAnnotationModel;
Since:3.0
/** * @since 3.0 */
public class ResourceTextFileBuffer extends ResourceFileBuffer implements ITextFileBuffer { private class DocumentListener implements IDocumentListener { @Override public void documentAboutToBeChanged(DocumentEvent event) { // do nothing } @Override public void documentChanged(DocumentEvent event) { if (fCanBeSaved && fSynchronizationStamp == event.getModificationStamp()) { fCanBeSaved= false; fManager.fireDirtyStateChanged(ResourceTextFileBuffer.this, fCanBeSaved); } else if (!fCanBeSaved) { fCanBeSaved= true; fManager.fireDirtyStateChanged(ResourceTextFileBuffer.this, fCanBeSaved); } } }
Reader chunk size.
/** * Reader chunk size. */
static final private int READER_CHUNK_SIZE= 2048;
Buffer size.
/** * Buffer size. */
static final private int BUFFER_SIZE= 8 * READER_CHUNK_SIZE;
Qualified name for the encoding key.
/** * Qualified name for the encoding key. */
static final private QualifiedName ENCODING_KEY= new QualifiedName(FileBuffersPlugin.PLUGIN_ID, "encoding"); //$NON-NLS-1$
Constant for representing the error status. This is considered a value object.
/** * Constant for representing the error status. This is considered a value object. */
static final private IStatus STATUS_ERROR= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, FileBuffersMessages.FileBuffer_status_error, null);
Constant denoting an empty set of properties
Since:3.1
/** * Constant denoting an empty set of properties * @since 3.1 */
private static final QualifiedName[] NO_PROPERTIES= new QualifiedName[0];
The element's document
/** The element's document */
protected IDocument fDocument;
The encoding used to create the document from the storage or null for workbench encoding.
/** The encoding used to create the document from the storage or <code>null</code> for workbench encoding. */
protected String fEncoding;
Internal document listener
/** Internal document listener */
protected IDocumentListener fDocumentListener= new DocumentListener();
The element's annotation model
/** The element's annotation model */
protected IAnnotationModel fAnnotationModel;
The encoding which has explicitly been set on the file.
/** The encoding which has explicitly been set on the file. */
private String fExplicitEncoding;
The BOM that needs to get written.
/** The BOM that needs to get written. */
private byte[] fBOM;
Lock for lazy creation of annotation model.
Since:3.2
/** * Lock for lazy creation of annotation model. * @since 3.2 */
private final Object fAnnotationModelCreationLock= new Object(); public ResourceTextFileBuffer(ResourceTextFileBufferManager manager) { super(manager); } @Override public IDocument getDocument() { return fDocument; } @Override public IAnnotationModel getAnnotationModel() { synchronized (fAnnotationModelCreationLock) { if (fAnnotationModel == null && !isDisconnected()) { fAnnotationModel= getManager().createAnnotationModel(fFile); if (fAnnotationModel != null) fAnnotationModel.connect(fDocument); } } return fAnnotationModel; }
Returns the file buffer manager.
Returns:the file buffer manager
Since:3.4
/** * Returns the file buffer manager. * * @return the file buffer manager * @since 3.4 */
private ResourceTextFileBufferManager getManager() { return (ResourceTextFileBufferManager)fManager; } @Override public String getEncoding() { return fEncoding; } @Override public void setEncoding(String encoding) { fEncoding= encoding; fExplicitEncoding= encoding; fBOM= null; try { fFile.setCharset(encoding, null); if (encoding == null) fEncoding= fFile.getCharset(); cacheBOM(); } catch (CoreException x) { handleCoreException(x); } } @Override public IStatus getStatus() { if (!isDisconnected()) { if (fStatus != null) return fStatus; return (fDocument == null ? STATUS_ERROR : Status.OK_STATUS); } return STATUS_ERROR; } @Override public IContentType getContentType() throws CoreException { try { if (isDirty()) { try(Reader reader = new DocumentReader(getDocument())) { IContentDescription desc= Platform.getContentTypeManager().getDescriptionFor(reader, fFile.getName(), NO_PROPERTIES); if (desc != null && desc.getContentType() != null) return desc.getContentType(); } } IContentDescription desc= fFile.getContentDescription(); if (desc != null && desc.getContentType() != null) return desc.getContentType(); return null; } catch (IOException x) { throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, NLSUtility.format(FileBuffersMessages.FileBuffer_error_queryContentDescription, fFile.getFullPath().toOSString()), x)); } } @Override protected void addFileBufferContentListeners() { if (fDocument != null) fDocument.addDocumentListener(fDocumentListener); } @Override protected void removeFileBufferContentListeners() { if (fDocument != null) fDocument.removeDocumentListener(fDocumentListener); } @Override protected void initializeFileBufferContent(IProgressMonitor monitor) throws CoreException { try { fEncoding= null; fExplicitEncoding= null; try { fEncoding= fFile.getPersistentProperty(ENCODING_KEY); } catch (CoreException x) { // we ignore exceptions here because we support the ENCODING_KEY property only for compatibility reasons } if (fEncoding != null) { // if we found an old encoding property, we try to migrate it to the new core.resources encoding support try { fExplicitEncoding= fEncoding; fFile.setCharset(fEncoding, monitor); // if successful delete old property fFile.setPersistentProperty(ENCODING_KEY, null); } catch (CoreException ex) { // log problem because we could not migrate the property successfully handleCoreException(ex); } cacheBOM(); } else { cacheEncodingState(); } fDocument= getManager().createEmptyDocument(fFile); setDocumentContent(fDocument, fFile, fEncoding); } catch (CoreException x) { fDocument= getManager().createEmptyDocument(fFile); fStatus= x.getStatus(); } }
Caches the BOM of the underlying file.
Throws:
  • CoreException – if reading of file's content description fails
/** * Caches the BOM of the underlying file. * * @throws CoreException if reading of file's content description fails */
protected void cacheBOM() throws CoreException { fBOM= null; IContentDescription description= fFile.getContentDescription(); if (description != null) fBOM= (byte[])description.getProperty(IContentDescription.BYTE_ORDER_MARK); } @Override protected void connected() { super.connected(); if (fAnnotationModel != null) fAnnotationModel.connect(fDocument); } /* * @see org.eclipse.core.internal.filebuffers.ResourceFileBuffer#disconnected() */ @Override protected void dispose() { try { fDocument.removePositionCategory(IDocument.DEFAULT_CATEGORY); } catch (BadPositionCategoryException ex) { // Category is already gone - no problem. } if (fAnnotationModel != null) fAnnotationModel.disconnect(fDocument); fDocument= null; super.dispose(); } @Override protected void commitFileBufferContent(IProgressMonitor monitor, boolean overwrite) throws CoreException { if (!isSynchronized() && !overwrite) { String message= NLSUtility.format(FileBuffersMessages.FileBuffer_error_outOfSync, getFileStore().toURI()); throw new CoreException(new Status(IStatus.WARNING, FileBuffersPlugin.PLUGIN_ID, IResourceStatus.OUT_OF_SYNC_LOCAL, message, null)); } String encoding= computeEncoding(); if (fBOM == IContentDescription.BOM_UTF_16LE && StandardCharsets.UTF_16.name().equals(encoding)) encoding= StandardCharsets.UTF_16LE.name(); Charset charset; try { charset= Charset.forName(encoding); } catch (UnsupportedCharsetException ex) { String message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_unsupported_encoding_message_arg, encoding); IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, ex); throw new CoreException(s); } catch (IllegalCharsetNameException ex) { String message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_illegal_encoding_message_arg, encoding); IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, ex); throw new CoreException(s); } CharsetEncoder encoder= charset.newEncoder(); encoder.onMalformedInput(CodingErrorAction.REPLACE); encoder.onUnmappableCharacter(CodingErrorAction.REPORT); InputStream stream; try { byte[] bytes; ByteBuffer byteBuffer= encoder.encode(CharBuffer.wrap(fDocument.get())); if (byteBuffer.hasArray()) bytes= byteBuffer.array(); else { bytes= new byte[byteBuffer.limit()]; byteBuffer.get(bytes); } stream= new ByteArrayInputStream(bytes, 0, byteBuffer.limit()); } catch (CharacterCodingException ex) { Assert.isTrue(ex instanceof UnmappableCharacterException); String message= NLSUtility.format(FileBuffersMessages.ResourceTextFileBuffer_error_charset_mapping_failed_message_arg, encoding); IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CHARSET_MAPPING_FAILED, message, ex); throw new CoreException(s); } /* * XXX: * This is a workaround for a corresponding bug in Java readers and writer, * see http://developer.java.sun.com/developer/bugParade/bugs/4508058.html */ if (fBOM == IContentDescription.BOM_UTF_8 && StandardCharsets.UTF_8.name().equals(encoding)) stream= new SequenceInputStream(new ByteArrayInputStream(IContentDescription.BOM_UTF_8), stream); if (fBOM == IContentDescription.BOM_UTF_16LE && StandardCharsets.UTF_16LE.name().equals(encoding)) stream= new SequenceInputStream(new ByteArrayInputStream(IContentDescription.BOM_UTF_16LE), stream); if (fFile.exists()) { // here the file synchronizer should actually be removed and afterwards added again. However, // we are already inside an operation, so the delta is sent AFTER we have added the listener fFile.setContents(stream, overwrite, true, monitor); // set synchronization stamp to know whether the file synchronizer must become active if (fDocument instanceof IDocumentExtension4) { fSynchronizationStamp= ((IDocumentExtension4)fDocument).getModificationStamp(); fFile.revertModificationStamp(fSynchronizationStamp); } else fSynchronizationStamp= fFile.getModificationStamp(); if (fAnnotationModel instanceof IPersistableAnnotationModel) { IPersistableAnnotationModel persistableModel= (IPersistableAnnotationModel) fAnnotationModel; persistableModel.commit(fDocument); } } else { SubMonitor subMonitor= SubMonitor.convert(monitor, FileBuffersMessages.ResourceTextFileBuffer_task_saving, 2); ContainerCreator creator= new ContainerCreator(fFile.getWorkspace(), fFile.getParent().getFullPath()); creator.createContainer(subMonitor.split(1)); fFile.create(stream, false, subMonitor.split(1)); // set synchronization stamp to know whether the file synchronizer must become active fSynchronizationStamp= fFile.getModificationStamp(); subMonitor.split(1); // TODO commit persistable annotation model } } private String computeEncoding() { // User-defined encoding has first priority if (fExplicitEncoding != null) return fExplicitEncoding; // Probe content try(Reader reader= new DocumentReader(fDocument)) { QualifiedName[] options= new QualifiedName[] { IContentDescription.CHARSET, IContentDescription.BYTE_ORDER_MARK }; IContentDescription description= Platform.getContentTypeManager().getDescriptionFor(reader, fFile.getName(), options); if (description != null) { String encoding= description.getCharset(); if (encoding != null) return encoding; } } catch (IOException ex) { // try next strategy } // Use file's encoding if the file has a BOM if (fBOM != null) return fEncoding; // Use parent chain try { return fFile.getParent().getDefaultCharset(); } catch (CoreException ex) { // Use global default return fManager.getDefaultEncoding(); } }
Internally caches the text resource's encoding.
Throws:
  • CoreException – if the encoding cannot be retrieved from the resource
Since:3.1
/** * Internally caches the text resource's encoding. * * @throws CoreException if the encoding cannot be retrieved from the resource * @since 3.1 */
protected void cacheEncodingState() throws CoreException { fExplicitEncoding= fFile.getCharset(false); if (fExplicitEncoding != null) fEncoding= fExplicitEncoding; else fEncoding= fFile.getCharset(); cacheBOM(); } @Override protected void handleFileContentChanged(boolean revert, boolean updateModificationStamp) throws CoreException { IDocument document= getManager().createEmptyDocument(fFile); IStatus status= null; try { cacheEncodingState(); setDocumentContent(document, fFile, fEncoding); } catch (CoreException x) { status= x.getStatus(); } String newContent= document.get(); boolean replaceContent= updateModificationStamp || !newContent.equals(fDocument.get()); if (replaceContent) fManager.fireBufferContentAboutToBeReplaced(this); removeFileBufferContentListeners(); fSynchronizationStamp= fFile.getModificationStamp(); if (replaceContent) { if (fDocument instanceof IDocumentExtension4) ((IDocumentExtension4)fDocument).set(newContent, fSynchronizationStamp); else fDocument.set(newContent); } fCanBeSaved= false; fStatus= status; addFileBufferContentListeners(); if (replaceContent) fManager.fireBufferContentReplaced(this); if (fAnnotationModel instanceof IPersistableAnnotationModel) { IPersistableAnnotationModel persistableModel= (IPersistableAnnotationModel) fAnnotationModel; try { if (revert) persistableModel.revert(fDocument); else persistableModel.reinitialize(fDocument); } catch (CoreException x) { fStatus= x.getStatus(); } } fManager.fireDirtyStateChanged(this, fCanBeSaved); }
Initializes the given document with the given stream using the given encoding.
Params:
  • document – the document to be initialized
  • file – the file which delivers the document content
  • encoding – the character encoding for reading the given stream
Throws:
/** * Initializes the given document with the given stream using the given encoding. * * @param document the document to be initialized * @param file the file which delivers the document content * @param encoding the character encoding for reading the given stream * @exception CoreException if the given stream can not be read */
private void setDocumentContent(IDocument document, IFile file, String encoding) throws CoreException { InputStream contentStream= file.getContents(); Reader in= null; try { if (encoding == null) encoding= fManager.getDefaultEncoding(); /* * XXX: * This is a workaround for a corresponding bug in Java readers and writer, * see http://developer.java.sun.com/developer/bugParade/bugs/4508058.html */ if (fBOM != null && StandardCharsets.UTF_8.name().equals(encoding)) { int n= 0; do { int bytes= contentStream.read(new byte[IContentDescription.BOM_UTF_8.length]); if (bytes == -1) throw new IOException(); n += bytes; } while (n < IContentDescription.BOM_UTF_8.length); } in= new BufferedReader(new InputStreamReader(contentStream, encoding), BUFFER_SIZE); StringBuilder buffer= new StringBuilder(BUFFER_SIZE); char[] readBuffer= new char[READER_CHUNK_SIZE]; int n= in.read(readBuffer); while (n > 0) { buffer.append(readBuffer, 0, n); n= in.read(readBuffer); } if (document instanceof IDocumentExtension4) ((IDocumentExtension4)document).set(buffer.toString(), fFile.getModificationStamp()); else document.set(buffer.toString()); } catch (IOException x) { String message= (x.getMessage() != null ? x.getMessage() : ""); //$NON-NLS-1$ IStatus s= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, x); throw new CoreException(s); } finally { try { if (in != null) in.close(); else contentStream.close(); } catch (IOException x) { } } } }