/*******************************************************************************
 * Copyright (c) 2000, 2019 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.jdt.internal.ui.javaeditor;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.osgi.framework.Bundle;

import org.eclipse.swt.SWT;

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.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
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.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelectionProvider;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.MultiEditorInput;

import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.TextEditorAction;

import org.eclipse.ui.editors.text.TextFileDocumentProvider;

import org.eclipse.compare.rangedifferencer.IRangeComparator;
import org.eclipse.compare.rangedifferencer.RangeDifference;
import org.eclipse.compare.rangedifferencer.RangeDifferencer;

import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.PreferenceConstants;

import org.eclipse.jdt.internal.ui.IJavaStatusConstants;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.compare.JavaCompareUtilities;
import org.eclipse.jdt.internal.ui.text.LineComparator;


/**
 * A number of routines for working with JavaElements in editors.
 *
 * Use 'isOpenInEditor' to test if an element is already open in a editor
 * Use 'openInEditor' to force opening an element in a editor
 * With 'getWorkingCopy' you get the working copy (element in the editor) of an element
 */
public class EditorUtility {

	/**
	 * Tests if a CU is currently shown in an editor
	 *
	 * @param inputElement the input element
	 * @return the IEditorPart if shown, null if element is not open in an editor
	 */
	public static IEditorPart isOpenInEditor(Object inputElement) {
		IEditorPart editor= findEditor(inputElement, false);
		if (editor != null)
			return editor;

		IEditorInput input= getEditorInput(inputElement);

		if (input != null) {
			IWorkbenchPage p= JavaPlugin.getActivePage();
			if (p != null) {
				return p.findEditor(input);
			}
		}

		return null;
	}

	/**
	 * Opens a Java editor for an element such as <code>IJavaElement</code>, <code>IFile</code>, or <code>IStorage</code>.
	 * The editor is activated by default.
	 *
	 * @param inputElement the input element
	 * @return an open editor or <code>null</code> if an external editor was opened
	 * @throws PartInitException if the editor could not be opened or the input element is not valid.
	 * Status code {@link IJavaStatusConstants#EDITOR_NO_EDITOR_INPUT} if opening the editor failed as
	 * no editor input could be created for the given element.
	 */
	public static IEditorPart openInEditor(Object inputElement) throws PartInitException {
		return openInEditor(inputElement, true);
	}

	/**
	 * Opens the editor currently associated with the given element (IJavaElement, IFile, IStorage...)
	 *
	 * @param inputElement the input element
	 * @param activate <code>true</code> if the editor should be activated
	 * @return an open editor or <code>null</code> if an external editor was opened
	 * @throws PartInitException if the editor could not be opened or the input element is not valid
	 * Status code {@link IJavaStatusConstants#EDITOR_NO_EDITOR_INPUT} if opening the editor failed as
	 * no editor input could be created for the given element.
	 */
	public static IEditorPart openInEditor(Object inputElement, boolean activate) throws PartInitException {

		if (inputElement instanceof IFile) {
			IFile file= (IFile) inputElement;
			if (!isClassFile(file))
				return openInEditor(file, activate);
			inputElement= JavaCore.createClassFileFrom(file);
		}

		IEditorPart editor= findEditor(inputElement, activate);
		if (editor != null)
			return editor;

		IEditorInput input= getEditorInput(inputElement);
		if (input == null)
			throwPartInitException(JavaEditorMessages.EditorUtility_no_editorInput, IJavaStatusConstants.EDITOR_NO_EDITOR_INPUT);

		return openInEditor(input, getEditorID(input), activate);
	}

	/**
	 * Tries to find the editor for the given input element.
	 *
	 * @param inputElement the input element
	 * @param activate <code>true</code> if the found editor should be activated
	 * @return the editor or <code>null</code>
	 * @since 3.5
	 */
	private static IEditorPart findEditor(Object inputElement, boolean activate) {
		/*
		 * Support to navigate inside non-primary working copy.
		 * For now we only support to navigate inside the currently
		 * active editor.
		 *
		 * XXX: once we have FileStoreEditorInput as API,
		 * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=111887
		 * we can fix this code by creating the correct editor input
		 * in getEditorInput(Object)
		 */
		if (inputElement instanceof IJavaElement) {
			ICompilationUnit cu= (ICompilationUnit)((IJavaElement)inputElement).getAncestor(IJavaElement.COMPILATION_UNIT);
			if (cu != null) {
				IWorkbenchPage page= JavaPlugin.getActivePage();
				IEditorPart editor= page != null ? editor= page.getActiveEditor() : null;
				if (editor != null) {
					boolean isCompareEditorInput= isCompareEditorInput(editor.getEditorInput());
					if (isCompareEditorInput || !JavaModelUtil.isPrimary(cu)) {
						IEditorInput editorInput;
						if (isCompareEditorInput)
							editorInput= editor.getAdapter(IEditorInput.class);
						else
							editorInput= editor.getEditorInput();
						IJavaElement editorCU= getEditorInputJavaElement(editorInput, false);
						if (cu.equals(editorCU)) {
							if (activate && page.getActivePart() != editor)
								page.activate(editor);
							return editor;
						}
					}
				}
			}
		}
		return null;
	}

	/*
	 * Avoid unnecessary loading of Compare plug-in
	 */
	private static boolean isComparePlugInActivated() {
		return Platform.getBundle("org.eclipse.compare").getState() == Bundle.ACTIVE; //$NON-NLS-1$
	}

	/*
	 * Avoid unnecessary loading of Compare plug-in
	 */
	public static boolean isCompareEditorInput(IEditorInput input) {
		return isComparePlugInActivated() && JavaCompareUtilities.isCompareEditorInput(input);
	}

	/**
	 * Selects a Java Element in an editor.
	 *
	 * @param part the editor part
	 * @param element the Java element to reveal
	 */
	public static void revealInEditor(IEditorPart part, IJavaElement element) {
		if (element == null)
			return;

		if (part instanceof JavaEditor) {
			((JavaEditor) part).setSelection(element);
			return;
		}

		// Support for non-Java editor
		try {
			ISourceRange range= null;
			if (element instanceof ICompilationUnit
					|| element instanceof IClassFile) {
				return;
			}

			if (element instanceof ISourceReference)
				range= ((ISourceReference)element).getNameRange();

			if (range != null)
				revealInEditor(part, range.getOffset(), range.getLength());
		} catch (JavaModelException e) {
			// don't reveal
		}
	}

	/**
	 * Selects and reveals the given region in the given editor part.
	 *
	 * @param part the editor part
	 * @param region the region
	 */
	public static void revealInEditor(IEditorPart part, IRegion region) {
		if (part != null && region != null)
			revealInEditor(part, region.getOffset(), region.getLength());
	}

	/**
	 * Selects and reveals the given offset and length in the given editor part.
	 * @param editor the editor part
	 * @param offset the offset
	 * @param length the length
	 */
	public static void revealInEditor(IEditorPart editor, final int offset, final int length) {
		if (editor instanceof ITextEditor) {
			((ITextEditor)editor).selectAndReveal(offset, length);
			return;
		}

		// Support for non-text editor - try IGotoMarker interface
		final IGotoMarker gotoMarkerTarget;
		if (editor instanceof IGotoMarker)
			gotoMarkerTarget= (IGotoMarker)editor;
		else
			gotoMarkerTarget= editor != null ? (IGotoMarker)editor.getAdapter(IGotoMarker.class) : null;
		if (gotoMarkerTarget != null) {
			final IEditorInput input= editor.getEditorInput();
			if (input instanceof IFileEditorInput) {
				WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
					@Override
					protected void execute(IProgressMonitor monitor) throws CoreException {
						IMarker marker= null;
						try {
							Map<String,Object> attributes = new HashMap<>();
							attributes.put(IMarker.CHAR_START, Integer.valueOf(offset));
							attributes.put(IMarker.CHAR_END, Integer.valueOf(offset + length));

							marker= ((IFileEditorInput)input).getFile().createMarker(IMarker.TEXT, attributes);

							gotoMarkerTarget.gotoMarker(marker);

						} finally {
							if (marker != null)
								marker.delete();
						}
					}
				};

				try {
					op.run(null);
				} catch (InvocationTargetException ex) {
					// reveal failed
				} catch (InterruptedException e) {
					Assert.isTrue(false, "this operation can not be canceled"); //$NON-NLS-1$
				}
			}
			return;
		}

		/*
		 * Workaround: send out a text selection
		 * XXX: Needs to be improved, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=32214
		 */
		if (editor != null && editor.getEditorSite().getSelectionProvider() != null) {
			IEditorSite site= editor.getEditorSite();
			if (site == null)
				return;

			ISelectionProvider provider= editor.getEditorSite().getSelectionProvider();
			if (provider == null)
				return;

			provider.setSelection(new TextSelection(offset, length));
		}
	}

	private static IEditorPart openInEditor(IFile file, boolean activate) throws PartInitException {
		if (file == null)
			throwPartInitException(JavaEditorMessages.EditorUtility_file_must_not_be_null);

		IWorkbenchPage p= JavaPlugin.getActivePage();
		if (p == null)
			throwPartInitException(JavaEditorMessages.EditorUtility_no_active_WorkbenchPage);

		IEditorPart editorPart= IDE.openEditor(p, file, activate);
		initializeHighlightRange(editorPart);
		return editorPart;
	}

	private static IEditorPart openInEditor(IEditorInput input, String editorID, boolean activate) throws PartInitException {
		Assert.isNotNull(input);
		Assert.isNotNull(editorID);

		IWorkbenchPage p= JavaPlugin.getActivePage();
		if (p == null)
			throwPartInitException(JavaEditorMessages.EditorUtility_no_active_WorkbenchPage);

		IEditorPart editorPart= p.openEditor(input, editorID, activate);
		initializeHighlightRange(editorPart);
		return editorPart;
	}

	private static void throwPartInitException(String message, int code) throws PartInitException {
		IStatus status= new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, code, message, null);
		throw new PartInitException(status);
	}

	private static void throwPartInitException(String message) throws PartInitException {
		throwPartInitException(message, IStatus.OK);
	}

	private static void initializeHighlightRange(IEditorPart editorPart) {
		if (editorPart instanceof ITextEditor) {
			IAction toggleAction= editorPart.getEditorSite().getActionBars().getGlobalActionHandler(ITextEditorActionDefinitionIds.TOGGLE_SHOW_SELECTED_ELEMENT_ONLY);
			boolean enable= toggleAction != null;
			if (enable && editorPart instanceof JavaEditor)
				enable= JavaPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SHOW_SEGMENTS);
			else
				enable= enable && toggleAction.isEnabled() && toggleAction.isChecked();
			if (enable) {
				if (toggleAction instanceof TextEditorAction) {
					// Reset the action
					((TextEditorAction)toggleAction).setEditor(null);
					// Restore the action
					((TextEditorAction)toggleAction).setEditor((ITextEditor)editorPart);
				} else {
					// Uncheck
					toggleAction.run();
					// Check
					toggleAction.run();
				}
			}
		}
	}

	public static String getEditorID(IEditorInput input) throws PartInitException {
		Assert.isNotNull(input);
		if (input instanceof IFileEditorInput)
			return IDE.getEditorDescriptor(((IFileEditorInput)input).getFile(), true, false).getId();

		String name= input.getName();

		if (input instanceof IClassFileEditorInput) {
			boolean hasSource;
			try {
				hasSource= ((IClassFileEditorInput) input).getClassFile().getSourceRange() != null;
			} catch (JavaModelException e) {
				hasSource= false;
			}

			if (!hasSource) {
				if (JavaModelUtil.MODULE_INFO_CLASS.equals(name)) {
					name= "module-info.class without source"; //$NON-NLS-1$
				} else {
					name= "*.class without source"; //$NON-NLS-1$
				}
			}
		}

		return IDE.getEditorDescriptor(name, true, false).getId();
	}

	/**
	 * Returns the given editor's input as Java element.
	 *
	 * @param editor the editor
	 * @param primaryOnly if <code>true</code> only primary working copies will be returned
	 * @return the given editor's input as <code>ITypeRoot</code> or <code>null</code> if none
	 * @since 3.2
	 */
	public static ITypeRoot getEditorInputJavaElement(IEditorPart editor, boolean primaryOnly) {
		Assert.isNotNull(editor);
		return getEditorInputJavaElement(editor.getEditorInput(), primaryOnly);
	}

	private static ITypeRoot getEditorInputJavaElement(IEditorInput editorInput, boolean primaryOnly) {
		if (editorInput == null)
			return null;

		ICompilationUnit cu= JavaPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editorInput, primaryOnly);
		if (cu != null)
			return cu;

		IJavaElement je= editorInput.getAdapter(IJavaElement.class);
		if (je instanceof ITypeRoot)
			return (ITypeRoot)je;

		return null;
	}

	private static IEditorInput getEditorInput(IJavaElement element) {
		while (element != null) {
			if (element instanceof ICompilationUnit) {
				ICompilationUnit unit= ((ICompilationUnit) element).getPrimary();
					IResource resource= unit.getResource();
					if (resource instanceof IFile)
						return new FileEditorInput((IFile) resource);
			}

			if (element instanceof IClassFile)
				return new InternalClassFileEditorInput((IClassFile) element);

			element= element.getParent();
		}

		return null;
	}

	public static IEditorInput getEditorInput(Object input) {
		if (input instanceof IJavaElement)
			return getEditorInput((IJavaElement) input);

		if (input instanceof IFile)
			return new FileEditorInput((IFile) input);

		if (JavaModelUtil.isOpenableStorage(input))
			return new JarEntryEditorInput((IStorage)input);

		return null;
	}

	/**
	 * Returns the Java element edited in the current active editor.
	 *
	 * @return the Java element or <code>null</code> if the active editor doesn't edit a Java element
	 */
	public static IJavaElement getActiveEditorJavaInput() {
		IWorkbenchPage page= JavaPlugin.getActivePage();
		if (page != null) {
			IEditorPart part= page.getActiveEditor();
			if (part != null) {
				IEditorInput editorInput= part.getEditorInput();
				if (editorInput != null) {
					return JavaUI.getEditorInputJavaElement(editorInput);
				}
			}
		}
		return null;
	}

	/**
	 * Maps the localized modifier name to a code in the same
	 * manner as #findModifier.
	 *
	 * @param modifierName the modifier name
	 * @return the SWT modifier bit, or <code>0</code> if no match was found
	 * @since 2.1.1
	 */
	public static int findLocalizedModifier(String modifierName) {
		if (modifierName == null)
			return 0;

		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.CTRL)))
			return SWT.CTRL;
		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.SHIFT)))
			return SWT.SHIFT;
		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.ALT)))
			return SWT.ALT;
		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.COMMAND)))
			return SWT.COMMAND;

		return 0;
	}

	/**
	 * Returns the modifier string for the given SWT modifier
	 * modifier bits.
	 *
	 * @param stateMask	the SWT modifier bits
	 * @return the modifier string
	 * @since 2.1.1
	 */
	public static String getModifierString(int stateMask) {
		String modifierString= ""; //$NON-NLS-1$
		if ((stateMask & SWT.CTRL) == SWT.CTRL)
			modifierString= appendModifierString(modifierString, SWT.CTRL);
		if ((stateMask & SWT.ALT) == SWT.ALT)
			modifierString= appendModifierString(modifierString, SWT.ALT);
		if ((stateMask & SWT.SHIFT) == SWT.SHIFT)
			modifierString= appendModifierString(modifierString, SWT.SHIFT);
		if ((stateMask & SWT.COMMAND) == SWT.COMMAND)
			modifierString= appendModifierString(modifierString,  SWT.COMMAND);

		return modifierString;
	}

	/**
	 * Appends to modifier string of the given SWT modifier bit
	 * to the given modifierString.
	 *
	 * @param modifierString	the modifier string
	 * @param modifier			an int with SWT modifier bit
	 * @return the concatenated modifier string
	 * @since 2.1.1
	 */
	private static String appendModifierString(String modifierString, int modifier) {
		if (modifierString == null)
			modifierString= ""; //$NON-NLS-1$
		String newModifierString= Action.findModifierString(modifier);
		if (modifierString.length() == 0)
			return newModifierString;
		return Messages.format(JavaEditorMessages.EditorUtility_concatModifierStrings, new String[] {modifierString, newModifierString});
	}

	/**
	 * Returns the Java project for a given editor input or <code>null</code> if no corresponding
	 * Java project exists.
	 *
	 * @param input the editor input
	 * @return the corresponding Java project
	 *
	 * @since 3.0
	 */
	public static IJavaProject getJavaProject(IEditorInput input) {
		IJavaProject jProject= null;
		if (input instanceof IFileEditorInput) {
			IProject project= ((IFileEditorInput)input).getFile().getProject();
			if (project != null) {
				jProject= JavaCore.create(project);
				if (!jProject.exists())
					jProject= null;
			}
		} else if (input instanceof IClassFileEditorInput) {
			jProject= ((IClassFileEditorInput)input).getClassFile().getJavaProject();
		}
		return jProject;
	}

	/**
	 * Returns the Java project for a given text editor or <code>null</code> if no corresponding
	 * Java project exists.
	 *
	 * @param editor the text editor
	 *
	 * @return the corresponding Java project
	 *
	 * @since 3.19
	 */
	public static IJavaProject getJavaProject(ITextEditor editor) {
		if (editor == null)
			return null;

		IJavaElement element= null;
		IEditorInput input= editor.getEditorInput();
		IDocumentProvider provider= editor.getDocumentProvider();
		if (provider instanceof ICompilationUnitDocumentProvider) {
			ICompilationUnitDocumentProvider cudp= (ICompilationUnitDocumentProvider) provider;
			element= cudp.getWorkingCopy(input);
		} else if (input instanceof IClassFileEditorInput) {
			IClassFileEditorInput cfei= (IClassFileEditorInput) input;
			element= cfei.getClassFile();
		}

		if (element == null) {
			if (input != null) {
				return EditorUtility.getJavaProject(input);
			}
			return null;
		}

		return element.getJavaProject();
	}

	/**
	 * Returns an array of all editors that have an unsaved content. If the identical content is
	 * presented in more than one editor, only one of those editor parts is part of the result.
	 * @param skipNonResourceEditors if <code>true</code>, editors whose inputs do not adapt to {@link IResource}
	 * are not saved
	 *
	 * @return an array of dirty editor parts
	 * @since 3.4
	 */
	public static IEditorPart[] getDirtyEditors(boolean skipNonResourceEditors) {
		Set<IEditorInput> inputs= new HashSet<>();
		List<IEditorPart> result= new ArrayList<>(0);
		IWorkbench workbench= PlatformUI.getWorkbench();
		for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
			for (IWorkbenchPage page : window.getPages()) {
				for (IEditorPart ep : page.getDirtyEditors()) {
					IEditorInput input= ep.getEditorInput();
					if (inputs.add(input)) {
						if (!skipNonResourceEditors || isResourceEditorInput(input)) {
							result.add(ep);
						}
					}
				}
			}
		}
		return result.toArray(new IEditorPart[result.size()]);
	}

	private static boolean isResourceEditorInput(IEditorInput input) {
		if (input instanceof MultiEditorInput) {
			IEditorInput[] inputs= ((MultiEditorInput) input).getInput();
			for (IEditorInput i : inputs) {
				if (i.getAdapter(IResource.class) != null) {
					return true;
				}
			}
		} else if (input.getAdapter(IResource.class) != null) {
			return true;
		}
		return false;
	}

	private static boolean isClassFile(IFile file) {
		IContentDescription contentDescription;
		try {
			contentDescription= file.getContentDescription();
		} catch (CoreException e) {
			contentDescription= null;
		}
		if (contentDescription == null)
			return false;

		IContentType contentType= contentDescription.getContentType();
		if (contentType == null)
			return false;

		return "org.eclipse.jdt.core.javaClass".equals(contentType.getId()); //$NON-NLS-1$
	}

	/**
	 * Returns the editors to save before performing global Java-related
	 * operations.
	 *
	 * @param saveUnknownEditors <code>true</code> iff editors with unknown buffer management should also be saved
	 * @return the editors to save
	 * @since 3.3
	 */
	public static IEditorPart[] getDirtyEditorsToSave(boolean saveUnknownEditors) {
		Set<IEditorInput> inputs= new HashSet<>();
		List<IEditorPart> result= new ArrayList<>(0);
		IWorkbench workbench= PlatformUI.getWorkbench();
		for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
			for (IWorkbenchPage page : window.getPages()) {
				for (IEditorPart ep : page.getDirtyEditors()) {
					IEditorInput input= ep.getEditorInput();
					if (!mustSaveDirtyEditor(ep, input, saveUnknownEditors))
						continue;

					if (inputs.add(input))
						result.add(ep);
				}
			}
		}
		return result.toArray(new IEditorPart[result.size()]);
	}

	/*
	 * @since 3.3
	 */
	private static boolean mustSaveDirtyEditor(IEditorPart ep, IEditorInput input, boolean saveUnknownEditors) {
		/*
		 * Goal: save all editors that could interfere with refactoring operations.
		 *
		 * Always save all editors for compilation units that are not working copies.
		 * (Leaving them dirty would cause problems, since the file buffer could have been
		 * modified but the Java model is not reconciled.)
		 *
		 * If <code>saveUnknownEditors</code> is <code>true</code>, save all editors
		 * whose implementation is probably not based on file buffers.
		 */
		IResource resource= input.getAdapter(IResource.class);
		if (resource == null)
			return saveUnknownEditors;

		IJavaElement javaElement= JavaCore.create(resource);
		if (javaElement instanceof ICompilationUnit) {
			ICompilationUnit cu= (ICompilationUnit) javaElement;
			if (!cu.isWorkingCopy()) {
				return true;
			}
		}

		if (! (ep instanceof ITextEditor))
			return saveUnknownEditors;

		ITextEditor textEditor= (ITextEditor) ep;
		IDocumentProvider documentProvider= textEditor.getDocumentProvider();
		if (! (documentProvider instanceof TextFileDocumentProvider))
			return saveUnknownEditors;

		return false;
	}

	/**
	 * Return the regions of all lines which have changed in the given buffer since the last save
	 * occurred. Each region in the result spans over the size of at least one line. If successive
	 * lines have changed a region spans over the size of all successive lines. The regions include
	 * line delimiters.
	 *
	 * @param buffer the buffer to compare contents from
	 * @param monitor to report progress to
	 * @return the regions of the changed lines
	 * @throws CoreException if something goes wrong
	 * @since 3.4
	 */
	public static IRegion[] calculateChangedLineRegions(final ITextFileBuffer buffer, final IProgressMonitor monitor) throws CoreException {
		final IRegion[][] result= new IRegion[1][];
		final IStatus[] errorStatus= new IStatus[] { Status.OK_STATUS };

		try {
			SafeRunner.run(new ISafeRunnable() {

				/*
				 * @see org.eclipse.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
				 */
				@Override
				public void handleException(Throwable exception) {
					JavaPlugin.log(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, IJavaStatusConstants.EDITOR_CHANGED_REGION_CALCULATION, exception.getLocalizedMessage(), exception));
					String msg= JavaEditorMessages.CompilationUnitDocumentProvider_error_calculatingChangedRegions;
					errorStatus[0]= new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, IJavaStatusConstants.EDITOR_CHANGED_REGION_CALCULATION, msg, exception);
					result[0]= null;
				}

				/*
				 * @see org.eclipse.core.runtime.ISafeRunnable#run()
				 */
				@Override
				public void run() throws Exception {
					monitor.beginTask(JavaEditorMessages.CompilationUnitDocumentProvider_calculatingChangedRegions_message, 20);
					IPath location= buffer.getLocation();

					ITextFileBufferManager fileBufferManager= FileBuffers.createTextFileBufferManager();
					fileBufferManager.connect(location, LocationKind.NORMALIZE, getSubProgressMonitor(monitor, 15));
					try {
						IDocument currentDocument= buffer.getDocument();
						IDocument oldDocument= fileBufferManager.getTextFileBuffer(location, LocationKind.NORMALIZE).getDocument();

						result[0]= getChangedLineRegions(oldDocument, currentDocument);
					} finally {
						fileBufferManager.disconnect(location, LocationKind.NORMALIZE, getSubProgressMonitor(monitor, 5));
						monitor.done();
					}
				}

				/**
				 * Return regions of all lines which differ comparing <code>oldDocument</code>s
				 * content with <code>currentDocument</code>s content. Successive lines are merged
				 * into one region.
				 *
				 * @param oldDocument a document containing the old content
				 * @param currentDocument a document containing the current content
				 * @return the changed regions
				 * @throws BadLocationException if fetching the line information fails
				 */
				private IRegion[] getChangedLineRegions(IDocument oldDocument, IDocument currentDocument) throws BadLocationException {
					/*
					 * Do not change the type of those local variables. We use Object
					 * here in order to prevent loading of the Compare plug-in at load
					 * time of this class.
					 */
					Object leftSide= new LineComparator(oldDocument);
					Object rightSide= new LineComparator(currentDocument);

					RangeDifference[] differences= RangeDifferencer.findDifferences((IRangeComparator) leftSide, (IRangeComparator) rightSide);

					//It holds that:
					//1. Ranges are sorted:
					//     forAll r1,r2 element differences: indexOf(r1)<indexOf(r2) -> r1.rightStart()<r2.rightStart();
					//2. Successive changed lines are merged into on RangeDifference
					//     forAll r1,r2 element differences: r1.rightStart()<r2.rightStart() -> r1.rightEnd()<r2.rightStart

					ArrayList<IRegion> regions= new ArrayList<>();
					for (RangeDifference curr : differences) {
						if (curr.kind() == RangeDifference.CHANGE && curr.rightLength() > 0) {
							int startLine= curr.rightStart();
							int endLine= curr.rightEnd() - 1;

							IRegion startLineRegion= currentDocument.getLineInformation(startLine);
							if (startLine == endLine) {
								regions.add(startLineRegion);
							} else {
								IRegion endLineRegion= currentDocument.getLineInformation(endLine);
								int startOffset= startLineRegion.getOffset();
								int endOffset= endLineRegion.getOffset() + endLineRegion.getLength();
								regions.add(new Region(startOffset, endOffset - startOffset));
							}
						}
					}

					return regions.toArray(new IRegion[regions.size()]);
				}
			});
		} finally {
			if (!errorStatus[0].isOK())
				throw new CoreException(errorStatus[0]);
		}

		return result[0];
	}

	/**
	 * Creates and returns a new sub-progress monitor for the
	 * given parent monitor.
	 *
	 * @param monitor the parent progress monitor
	 * @param ticks the number of work ticks allocated from the parent monitor
	 * @return the new sub-progress monitor
	 * @since 3.4
	 */
	private static IProgressMonitor getSubProgressMonitor(IProgressMonitor monitor, int ticks) {
		if (monitor != null)
			return new SubProgressMonitor(monitor, ticks, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);

		return new NullProgressMonitor();
	}

	private EditorUtility() {
	}

	/**
	 * Returns the currently active java editor, or <code>null</code> if it
	 * cannot be determined.
	 *
	 * @return  the currently active java editor, or <code>null</code>
	 */
	public static JavaEditor getActiveJavaEditor() {
		IEditorPart part= JavaPlugin.getActivePage().getActiveEditor();
		if (part instanceof JavaEditor)
			return (JavaEditor) part;
		else
			return null;
	}

}
