/*******************************************************************************
 * Copyright (c) 2000, 2012 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
 *     Andrew Ferguson (Symbian) - [api] enable document setup participants to customize behavior based on resource being opened - https://bugs.eclipse.org/bugs/show_bug.cgi?id=208881
 *******************************************************************************/
package org.eclipse.core.internal.filebuffers;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.filesystem.URIUtil;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;

import org.eclipse.core.filebuffers.IAnnotationModelFactory;
import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
import org.eclipse.core.filebuffers.IDocumentSetupParticipantExtension;
import org.eclipse.core.filebuffers.IFileBuffer;
import org.eclipse.core.filebuffers.IFileBufferListener;
import org.eclipse.core.filebuffers.IFileBufferStatusCodes;
import org.eclipse.core.filebuffers.ISynchronizationContext;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.source.IAnnotationModel;


/**
 * @since 3.0
 */
public class TextFileBufferManager implements ITextFileBufferManager {

	private static abstract class SafeNotifier implements ISafeRunnable {
		@Override
		public void handleException(Throwable ex) {
			// NOTE: Logging is done by SafeRunner
		}
	}

	protected static final IContentType TEXT_CONTENT_TYPE= Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);

	private Map<IPath, AbstractFileBuffer> fFilesBuffers= new HashMap<>();
	private Map<IFileStore, FileStoreFileBuffer> fFileStoreFileBuffers= new HashMap<>();
	private List<IFileBufferListener> fFileBufferListeners= new ArrayList<>();
	protected ExtensionsRegistry fRegistry;
	private ISynchronizationContext fSynchronizationContext;


	public TextFileBufferManager()  {
		fRegistry= new ExtensionsRegistry();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.3, replaced by {@link #connect(IPath, LocationKind, IProgressMonitor)}
	 */
	@Deprecated
	@Override
	public void connect(IPath location, IProgressMonitor monitor) throws CoreException {
		connect(location, LocationKind.NORMALIZE, monitor);
	}

	@Override
	public void connect(IPath location, LocationKind locationKind, IProgressMonitor monitor) throws CoreException {
		Assert.isNotNull(location);
		if (locationKind == LocationKind.NORMALIZE)
			location= normalizeLocation(location);

		AbstractFileBuffer fileBuffer= null;
		synchronized (fFilesBuffers) {
			fileBuffer= internalGetFileBuffer(location);
			if (fileBuffer != null)  {
				fileBuffer.connect();
				return;
			}
		}

		fileBuffer= createFileBuffer(location, locationKind);
		if (fileBuffer == null)
			throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CREATION_FAILED, FileBuffersMessages.FileBufferManager_error_canNotCreateFilebuffer, null));

		fileBuffer.create(location, monitor);

		synchronized (fFilesBuffers) {
			AbstractFileBuffer oldFileBuffer= internalGetFileBuffer(location);
			if (oldFileBuffer != null) {
				fileBuffer.disconnect();
				fileBuffer.dispose();
				oldFileBuffer.connect();
				return;
			}
			fileBuffer.connect();
			fFilesBuffers.put(location, fileBuffer);
		}

		// Do notification outside synchronized block
		fireBufferCreated(fileBuffer);
	}

	@Override
	public void connectFileStore(IFileStore fileStore, IProgressMonitor monitor) throws CoreException {
		Assert.isLegal(fileStore != null);

		FileStoreFileBuffer fileBuffer= null;
		synchronized (fFileStoreFileBuffers) {
			fileBuffer= internalGetFileBuffer(fileStore);
			if (fileBuffer != null)  {
				fileBuffer.connect();
				return;
			}
		}

		fileBuffer= createFileBuffer(fileStore);
		if (fileBuffer == null)
			throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CREATION_FAILED, FileBuffersMessages.FileBufferManager_error_canNotCreateFilebuffer, null));

		fileBuffer.create(fileStore, monitor);

		synchronized (fFileStoreFileBuffers) {
			AbstractFileBuffer oldFileBuffer= internalGetFileBuffer(fileStore);
			if (oldFileBuffer != null) {
				fileBuffer.disconnect();
				fileBuffer.dispose();
				oldFileBuffer.connect();
				return;
			}
			fileBuffer.connect();
			fFileStoreFileBuffers.put(fileStore, fileBuffer);
		}

		// Do notification outside synchronized block
		fireBufferCreated(fileBuffer);
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.3, replaced by {@link #disconnect(IPath, LocationKind, IProgressMonitor)}
	 */
	@Deprecated
	@Override
	public void disconnect(IPath location, IProgressMonitor monitor) throws CoreException {
		disconnect(location, LocationKind.NORMALIZE, monitor);
	}

	/*
	 * @since 3.3
	 */
	protected IPath normalizeLocation(IPath location) {
		return location;
	}

	@Override
	public void disconnect(IPath location, LocationKind locationKind, IProgressMonitor monitor) throws CoreException {
		Assert.isNotNull(location);
		if (locationKind == LocationKind.NORMALIZE)
			location= normalizeLocation(location);

		AbstractFileBuffer fileBuffer;
		synchronized (fFilesBuffers) {
			fileBuffer= internalGetFileBuffer(location);
			if (fileBuffer == null)
				return;

			fileBuffer.disconnect();
			if (!fileBuffer.isDisconnected())
				return;

			fFilesBuffers.remove(location);
		}

		// Do notification outside synchronized block
		fireBufferDisposed(fileBuffer);
		fileBuffer.dispose();
	}

	@Override
	public void disconnectFileStore(IFileStore fileStore, IProgressMonitor monitor) throws CoreException {
		Assert.isLegal(fileStore != null);

		AbstractFileBuffer fileBuffer;
		synchronized (fFileStoreFileBuffers) {
			fileBuffer= internalGetFileBuffer(fileStore);
			if (fileBuffer == null)
				return;

			fileBuffer.disconnect();
			if (!fileBuffer.isDisconnected())
				return;

			fFileStoreFileBuffers.remove(fileStore);
		}

		// Do notification outside synchronized block
		fireBufferDisposed(fileBuffer);
		fileBuffer.dispose();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.2, replaced by {@link #isTextFileLocation(IPath, boolean)}
	 */
	@Deprecated
	@Override
	public boolean isTextFileLocation(IPath location) {
		return isTextFileLocation(location, false);
	}

	/**
	 * Returns whether a file store at the given location is or can be considered a
	 * text file. If the file store exists, the concrete content type of the file store is
	 * checked. If the concrete content type for the existing file store can not be
	 * determined, this method returns <code>!strict</code>. If the file store does
	 * not exist, it is checked whether a text content type is associated with
	 * the given location. If no content type is associated with the location,
	 * this method returns <code>!strict</code>.
	 * <p>
	 * The provided location is either a full path of a workspace resource or an
	 * absolute path in the local file system. The file buffer manager does not
	 * resolve the location of workspace resources in the case of linked
	 * resources.
	 * </p>
	 *
	 * @param fileStore	file store to check
	 * @param strict	<code>true</code> if a file with unknown content type
	 * 					is not treated as text file, <code>false</code> otherwise
	 * @return <code>true</code> if the location is a text file location
	 * @since 3.3
	 */
	protected boolean isTextFileLocation(IFileStore fileStore, boolean strict) {
		if (fileStore == null)
			return false;

		IContentTypeManager manager= Platform.getContentTypeManager();
		IFileInfo fileInfo= fileStore.fetchInfo();
		if (fileInfo.exists()) {

			try(InputStream is= fileStore.openInputStream(EFS.NONE, null)) {
				IContentDescription description= manager.getDescriptionFor(is, fileStore.getName(), IContentDescription.ALL);
				if (description != null) {
					IContentType type= description.getContentType();
					if (type != null)
						return type.isKindOf(TEXT_CONTENT_TYPE);
				}
			} catch (CoreException | IOException ex) {
				// ignore: API specification tells return true if content type can't be determined
			}

			return !strict;

		}

		IContentType[] contentTypes= manager.findContentTypesFor(fileStore.getName());
		if (contentTypes != null && contentTypes.length > 0) {
			for (IContentType contentType : contentTypes)
				if (contentType.isKindOf(TEXT_CONTENT_TYPE))
					return true;
			return false;
		}
		return !strict;
	}

	@Override
	public boolean isTextFileLocation(IPath location, boolean strict) {
		Assert.isNotNull(location);
		location= normalizeLocation(location);
		try {
			return isTextFileLocation(EFS.getStore(URIUtil.toURI(location)), strict);
		} catch (CoreException ex) {
			return false;
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.3, replaced by {@link #getFileBuffer(IPath, LocationKind)}
	 */
	@Deprecated
	@Override
	public IFileBuffer getFileBuffer(IPath location) {
		return getFileBuffer(location, LocationKind.NORMALIZE);
	}

	@Override
	public IFileBuffer getFileBuffer(IPath location, LocationKind locationKind) {
		if (locationKind == LocationKind.NORMALIZE)
			location= normalizeLocation(location);
		return internalGetFileBuffer(location);
	}

	@Override
	public IFileBuffer getFileStoreFileBuffer(IFileStore fileStore) {
		Assert.isLegal(fileStore != null);
		return internalGetFileBuffer(fileStore);
	}

	private AbstractFileBuffer internalGetFileBuffer(IPath location) {
		synchronized (fFilesBuffers) {
			return fFilesBuffers.get(location);
		}
	}

	private FileStoreFileBuffer internalGetFileBuffer(IFileStore fileStore) {
		synchronized (fFileStoreFileBuffers) {
			return fFileStoreFileBuffers.get(fileStore);
		}
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.3, replaced by {@link #getTextFileBuffer(IPath, LocationKind)}
	 */
	@Deprecated
	@Override
	public ITextFileBuffer getTextFileBuffer(IPath location) {
		return getTextFileBuffer(location, LocationKind.NORMALIZE);
	}

	@Override
	public ITextFileBuffer getTextFileBuffer(IPath location, LocationKind locationKind) {
		return (ITextFileBuffer)getFileBuffer(location, locationKind);
	}

	@Override
	public ITextFileBuffer getFileStoreTextFileBuffer(IFileStore fileStore) {
		Assert.isLegal(fileStore != null);
		return (ITextFileBuffer)getFileStoreFileBuffer(fileStore);
	}

	@Override
	public ITextFileBuffer getTextFileBuffer(IDocument document) {
		Assert.isLegal(document != null);
		Iterator<AbstractFileBuffer> iter;
		synchronized (fFilesBuffers) {
			iter= new ArrayList<>(fFilesBuffers.values()).iterator();
		}

		while (iter.hasNext()) {
			Object buffer= iter.next();
			if (buffer instanceof ITextFileBuffer) {
				ITextFileBuffer textFileBuffer= (ITextFileBuffer)buffer;
				if (textFileBuffer.getDocument() == document) {
					if (!((AbstractFileBuffer)textFileBuffer).isDisconnected())
						return textFileBuffer;
					return null;
				}
			}
		}
		synchronized (fFileStoreFileBuffers) {
			iter= new ArrayList<AbstractFileBuffer>(fFileStoreFileBuffers.values()).iterator();
		}
		while (iter.hasNext()) {
			Object buffer= iter.next();
			if (buffer instanceof ITextFileBuffer) {
				ITextFileBuffer textFileBuffer= (ITextFileBuffer)buffer;
				if (textFileBuffer.getDocument() == document) {
					if (!((AbstractFileBuffer)textFileBuffer).isDisconnected())
						return textFileBuffer;
				}
			}
		}
		return null;
	}

	@Override
	public IFileBuffer[] getFileBuffers() {
		synchronized (fFilesBuffers) {
			Collection<AbstractFileBuffer> values= fFilesBuffers.values();
			return values.toArray(new IFileBuffer[values.size()]);
		}
	}

	@Override
	public IFileBuffer[] getFileStoreFileBuffers() {
		synchronized (fFileStoreFileBuffers) {
			Collection<FileStoreFileBuffer> values= fFileStoreFileBuffers.values();
			return values.toArray(new IFileBuffer[values.size()]);
		}
	}

	@Override
	public String getDefaultEncoding() {
		return System.getProperty("file.encoding"); //$NON-NLS-1$;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.3, replaced by {@link #createEmptyDocument(IPath, LocationKind)}
	 */
	@Deprecated
	@Override
	public IDocument createEmptyDocument(IPath location) {
		return createEmptyDocument(location, LocationKind.NORMALIZE);
	}

	@Override
	public IDocument createEmptyDocument(final IPath location, final LocationKind locationKind) {
		IDocument documentFromFactory= createDocumentFromFactory(location, locationKind);
		final IDocument document;
		if (documentFromFactory != null)
			document= documentFromFactory;
		else
			document= new SynchronizableDocument();

		if (location == null)
			return document;

		// Set the initial line delimiter
		if (document instanceof IDocumentExtension4) {
			String initalLineDelimiter= getLineDelimiterPreference(location, locationKind);
			if (initalLineDelimiter != null)
				((IDocumentExtension4)document).setInitialLineDelimiter(initalLineDelimiter);
		}

		final IDocumentSetupParticipant[] participants= fRegistry.getDocumentSetupParticipants(location, locationKind);
		if (participants != null) {
			for (final IDocumentSetupParticipant participant : participants) {
				ISafeRunnable runnable= new ISafeRunnable() {
					@Override
					public void run() throws Exception {
						if (participant instanceof IDocumentSetupParticipantExtension)
							((IDocumentSetupParticipantExtension)participant).setup(document, location, locationKind);
						else
							participant.setup(document);

						if (document.getDocumentPartitioner() != null) {
							String message= NLSUtility.format(FileBuffersMessages.TextFileBufferManager_warning_documentSetupInstallsDefaultPartitioner, participant.getClass());
							IStatus status= new Status(IStatus.WARNING, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, message, null);
							Platform.getLog(ResourceTextFileBufferManager.class).log(status);
						}
					}
					@Override
					public void handleException(Throwable t) {
						IStatus status= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, FileBuffersMessages.TextFileBufferManager_error_documentSetupFailed, t);
						Platform.getLog(ResourceTextFileBufferManager.class).log(status);
					}
				};
				SafeRunner.run(runnable);
			}
		}

		return document;
	}

	/**
	 * Helper to get rid of deprecation warnings.
	 *
	 * @param location the location of the file to be connected
	 * @param locationKind the kind of the given location
	 * @return the created empty document or <code>null</code> if none got created
	 * @since 3.5
	 * @deprecated As of 3.5
	 */
	@Deprecated
	private IDocument createDocumentFromFactory(final IPath location, final LocationKind locationKind) {
		final IDocument[] runnableResult= new IDocument[1];
		if (location != null) {
			final org.eclipse.core.filebuffers.IDocumentFactory factory= fRegistry.getDocumentFactory(location, locationKind);
			if (factory != null) {
				ISafeRunnable runnable= new ISafeRunnable() {
					@Override
					public void run() throws Exception {
						runnableResult[0]= factory.createDocument();
					}
					@Override
					public void handleException(Throwable t) {
						IStatus status= new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IStatus.OK, FileBuffersMessages.TextFileBufferManager_error_documentFactoryFailed, t);
						Platform.getLog(ResourceTextFileBufferManager.class).log(status);
					}
				};
				SafeRunner.run(runnable);
			}
		}
		return runnableResult[0];
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.3, replaced by {@link #createAnnotationModel(IPath, LocationKind)}
	 */
	@Deprecated
	@Override
	public IAnnotationModel createAnnotationModel(IPath location) {
		return createAnnotationModel(location, LocationKind.NORMALIZE);
	}

	@Override
	public IAnnotationModel createAnnotationModel(IPath location, LocationKind locationKind) {
		Assert.isNotNull(location);
		IAnnotationModelFactory factory= fRegistry.getAnnotationModelFactory(location, locationKind);
		if (factory != null)
			return factory.createAnnotationModel(location);
		return null;
	}

	@Override
	public void addFileBufferListener(IFileBufferListener listener) {
		Assert.isNotNull(listener);
		synchronized (fFileBufferListeners) {
			if (!fFileBufferListeners.contains(listener))
				fFileBufferListeners.add(listener);
		}
	}

	@Override
	public void removeFileBufferListener(IFileBufferListener listener) {
		Assert.isNotNull(listener);
		synchronized (fFileBufferListeners) {
			fFileBufferListeners.remove(listener);
		}
	}

	@Override
	public void setSynchronizationContext(ISynchronizationContext context) {
		fSynchronizationContext= context;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.1, replaced by
	 *             {@link org.eclipse.core.filebuffers.IFileBuffer#requestSynchronizationContext()}
	 */
	@Deprecated
	@Override
	public void requestSynchronizationContext(IPath location) {
		Assert.isNotNull(location);
		location= normalizeLocation(location);

		AbstractFileBuffer fileBuffer= internalGetFileBuffer(location);
		if (fileBuffer != null)
			fileBuffer.requestSynchronizationContext();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @deprecated As of 3.1, replaced by {@link IFileBuffer#releaseSynchronizationContext()}
	 */
	@Deprecated
	@Override
	public void releaseSynchronizationContext(IPath location) {
		Assert.isNotNull(location);
		location= normalizeLocation(location);

		AbstractFileBuffer fileBuffer= internalGetFileBuffer(location);
		if (fileBuffer != null)
			fileBuffer.releaseSynchronizationContext();
	}

	@Override
	public void execute(Runnable runnable) {
		if (fSynchronizationContext != null)
			fSynchronizationContext.run(runnable);
		else
			runnable.run();
	}


	private AbstractFileBuffer createFileBuffer(IPath location, LocationKind locationKind) {
		/*
		 * XXX: the following code is commented out for performance
		 * reasons and because we do not yet create a special binary
		 * file buffer.
		 */
//		if (isTextFileLocation(location, false))
//			return createTextFileBuffer(location);
//		return createBinaryFileBuffer(location, locationKind);
		return createTextFileBuffer(location, locationKind);
	}

	/**
	 * Creates a text file buffer for the given path.
	 *
	 * @param location the location of the file to be connected
	 * @param locationKind the kind of the given location
	 * @return the text file buffer
	 * @since 3.3
	 */
	protected AbstractFileBuffer createTextFileBuffer(IPath location, LocationKind locationKind) {
		Assert.isLegal(locationKind != LocationKind.IFILE);
		return new FileStoreTextFileBuffer(this);
	}

//	private AbstractFileBuffer createBinaryFileBuffer(IPath location, LocationKind locationKind) {
//		// XXX: should return a binary file buffer - using text file buffer for now
//		return createTextFileBuffer(location, locationKind);
//	}

	private FileStoreFileBuffer createFileBuffer(IFileStore location) {
		/*
		 * XXX: the following code is commented out for performance
		 * reasons and because we do not yet create a special binary
		 * file buffer.
		 */
//		if (isTextFileLocation(location, false))
//			return createTextFileBuffer(location);
//		return createBinaryFileBuffer(location);
		return createTextFileBuffer(location);

	}

	/**
	 * Creates a text file buffer for the given file store.
	 *
	 * @param location the file store
	 * @return the text file buffer
	 * @since 3.3
	 */
	protected FileStoreFileBuffer createTextFileBuffer(IFileStore location) {
		return new FileStoreTextFileBuffer(this);
	}

//	private FileStoreFileBuffer createBinaryFileBuffer(FileStore location) {
//		// XXX: should return a binary file buffer - using text file buffer for now
//		return createTextFileBuffer(location);
//	}

	private Iterator<IFileBufferListener> getFileBufferListenerIterator() {
		synchronized (fFileBufferListeners) {
			return new ArrayList<>(fFileBufferListeners).iterator();
		}
	}

	protected void fireDirtyStateChanged(final IFileBuffer buffer, final boolean isDirty) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.dirtyStateChanged(buffer, isDirty);
				}
			});
		}
	}

	protected void fireBufferContentAboutToBeReplaced(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.bufferContentAboutToBeReplaced(buffer);
				}
			});
		}
	}

	protected void fireBufferContentReplaced(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.bufferContentReplaced(buffer);
				}
			});
		}
	}

	protected void fireUnderlyingFileMoved(final IFileBuffer buffer, final IPath target) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.underlyingFileMoved(buffer, target);
				}
			});
		}
	}

	protected void fireUnderlyingFileDeleted(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.underlyingFileDeleted(buffer);
				}
			});
		}
	}

	protected void fireStateValidationChanged(final IFileBuffer buffer, final boolean isStateValidated) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.stateValidationChanged(buffer, isStateValidated);
				}
			});
		}
	}

	protected void fireStateChanging(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.stateChanging(buffer);
				}
			});
		}
	}

	protected void fireStateChangeFailed(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.stateChangeFailed(buffer);
				}
			});
		}
	}

	protected void fireBufferCreated(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.bufferCreated(buffer);
				}
			});
		}
	}

	protected void fireBufferDisposed(final IFileBuffer buffer) {
		Iterator<IFileBufferListener> e= getFileBufferListenerIterator();
		while (e.hasNext()) {
			final IFileBufferListener l= e.next();
			SafeRunner.run(new SafeNotifier() {
				@Override
				public void run() {
					l.bufferDisposed(buffer);
				}
			});
		}
	}

	@Override
	public void validateState(final IFileBuffer[] fileBuffers, IProgressMonitor monitor, final Object computationContext) throws CoreException {
	}

	/**
	 * Returns the line delimiter to be used by the given location.
	 *
	 * @param location the location of the file to be connected
	 * @param locationKind the kind of the given location
	 * @return the line delimiter
	 * @since 3.3
	 */
	protected String getLineDelimiterPreference(IPath location, LocationKind locationKind) {
		return System.lineSeparator();
	}

}
