/*******************************************************************************
 * Copyright (c) 2000, 2017 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
 *     Genady Beryozkin <eclipse@genady.org> - [misc] Display values for constant fields in the Javadoc view - https://bugs.eclipse.org/bugs/show_bug.cgi?id=204914
 *     Brock Janiczak <brockj@tpg.com.au> - [implementation] Streams not being closed in Javadoc views - https://bugs.eclipse.org/bugs/show_bug.cgi?id=214854
 *     Benjamin Muskalla <bmuskalla@innoopract.com> - [javadoc view] NPE on enumerations - https://bugs.eclipse.org/bugs/show_bug.cgi?id=223586
 *     Stephan Herrmann - Contribution for Bug 403917 - [1.8] Render TYPE_USE annotations in Javadoc hover/view
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.infoviews;

import java.io.Reader;
import java.io.StringReader;
import java.net.URL;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;

import org.eclipse.core.resources.IFile;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.internal.text.html.BrowserInput;
import org.eclipse.jface.internal.text.html.HTMLPrinter;
import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;

import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;

import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.IAbstractTextEditorHelpContextIds;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IModularClassFile;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;

import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.javadoc.JavaDocLocations;
import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.ui.IContextMenuConstants;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
import org.eclipse.jdt.ui.actions.JdtActionConstants;
import org.eclipse.jdt.ui.actions.OpenAttachedJavadocAction;
import org.eclipse.jdt.ui.text.IJavaPartitions;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.actions.ActionMessages;
import org.eclipse.jdt.internal.ui.actions.SimpleSelectionProvider;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover;
import org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover.FallbackInformationPresenter;
import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2;
import org.eclipse.jdt.internal.ui.viewsupport.BindingLinkedLabelComposer;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks;


/**
 * View which shows Javadoc for a given Java element.
 *
 * FIXME: As of 3.0 selectAll() and getSelection() is not working
 *			see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
 *
 * @since 3.0
 */
public class JavadocView extends AbstractInfoView {

	/**
	 * Implementation of a {@link BrowserInput} using
	 * a {@link IJavaElement} as input.
	 *
	 * @since 3.4
	 */
	private static final class JavaElementBrowserInput extends BrowserInput {

		private final IJavaElement fInput;

		public JavaElementBrowserInput(BrowserInput previous, IJavaElement inputElement) {
			super(previous);
			Assert.isNotNull(inputElement);
			fInput= inputElement;
		}

		@Override
		public Object getInputElement() {
			return fInput;
		}

		@Override
		public String getInputName() {
			return fInput.getElementName();
		}
	}

	/**
	 * Implementation of a {@link BrowserInput} using an
	 * {@link URL} as input.
	 *
	 * @since 3.4
	 */
	private static class URLBrowserInput extends BrowserInput {

		private final URL fURL;

		public URLBrowserInput(BrowserInput previous, URL url) {
			super(previous);
			Assert.isNotNull(url);
			fURL= url;
		}

		@Override
		public Object getInputElement() {
			return fURL;
		}

		@Override
		public String getInputName() {
			return fURL.toExternalForm();
		}
	}

	/**
	 * Action to go forward in the history.
	 *
	 * @since 3.4
	 */
	private final class ForthAction extends Action {

		public ForthAction() {
			setText(InfoViewMessages.JavadocView_action_forward_name);
			ISharedImages images= PlatformUI.getWorkbench().getSharedImages();
			setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD));
			setDisabledImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD_DISABLED));

			update();
		}

		public void update() {
			if (fCurrent != null && fCurrent.getNext() != null) {
				BrowserInput element= fCurrent.getNext();
				setToolTipText(Messages.format(InfoViewMessages.JavadocView_action_forward_enabledTooltip, BasicElementLabels.getJavaElementName(element.getInputName())));
				setEnabled(true);
			} else {
				setToolTipText(InfoViewMessages.JavadocView_action_forward_disabledTooltip);
				setEnabled(false);
			}
		}

		@Override
		public void run() {
			setInput(fCurrent.getNext());
		}

	}

	/**
	 * Action to go backwards in the history.
	 *
	 * @since 3.4
	 */
	private final class BackAction extends Action {

		public BackAction() {
			setText(InfoViewMessages.JavadocView_action_back_name);
			ISharedImages images= PlatformUI.getWorkbench().getSharedImages();
			setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_BACK));
			setDisabledImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_BACK_DISABLED));

			update();
		}

		private void update() {
			if (fCurrent != null && fCurrent.getPrevious() != null) {
				BrowserInput element= fCurrent.getPrevious();
				setToolTipText(Messages.format(InfoViewMessages.JavadocView_action_back_enabledTooltip, BasicElementLabels.getJavaElementName(element.getInputName())));
				setEnabled(true);
			} else {
				setToolTipText(InfoViewMessages.JavadocView_action_back_disabledTooltip);
				setEnabled(false);
			}
		}

		@Override
		public void run() {
			setInput(fCurrent.getPrevious());
		}
	}

	/**
	 * Action to open the selection in an external browser. If the selection is a java element its
	 * corresponding javadoc is shown if possible. If it is an URL the URL's content is shown.
	 *
	 * The action is disabled if the selection cannot be opened.
	 *
	 * @since 3.6
	 */
	private static class OpenInBrowserAction extends OpenAttachedJavadocAction {

		/**
		 * Create a new ShowExternalJavadocAction
		 *
		 * @param site the site
		 */
		public OpenInBrowserAction(IWorkbenchSite site) {
			super(site);
		}

		@Override
		public void selectionChanged(IStructuredSelection structuredSelection) {
			super.selectionChanged(structuredSelection);
			Object element= structuredSelection.getFirstElement();
			if (element instanceof URL) {
				setText(InfoViewMessages.OpenInBrowserAction_url_label);
				setToolTipText(InfoViewMessages.OpenInBrowserAction_url_toolTip);
			} else {
				setText(ActionMessages.OpenAttachedJavadocAction_label);
				setToolTipText(ActionMessages.OpenAttachedJavadocAction_tooltip);
			}
		}

		@Override
		public void run(IStructuredSelection selection) {
			if (!canEnableFor(selection))
				return;

			Object element= selection.getFirstElement();
			if (element instanceof IJavaElement)
				super.run(selection);
			else
				open((URL)element);

		}

		/*
		 * @see org.eclipse.jdt.ui.actions.OpenAttachedJavadocAction#canEnableFor(org.eclipse.jface.viewers.IStructuredSelection)
		 */
		@Override
		protected boolean canEnableFor(IStructuredSelection selection) {
			if (selection.size() != 1)
				return false;

			Object element= selection.getFirstElement();
			return element instanceof URL || super.canEnableFor(selection);
		}

	}


	/**
	 * Preference key for the preference whether to show a dialog
	 * when the SWT Browser widget is not available.
	 * @since 3.0
	 */
	private static final String DO_NOT_WARN_PREFERENCE_KEY= "JavadocView.error.doNotWarn"; //$NON-NLS-1$

	// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=73558
	private static final boolean WARNING_DIALOG_ENABLED= false;

	/** The HTML widget. */
	private Browser fBrowser;
	/** The text widget. */
	private StyledText fText;
	/** The information presenter. */
	private HTMLTextPresenter fPresenter;
	/** The text presentation. */
	private final TextPresentation fPresentation= new TextPresentation();
	/** The select all action */
	private SelectAllAction fSelectAllAction;
	/** The style sheet (css) */
	private static String fgStyleSheet;
	/**
	 * <code>true</code> once the style sheet has been loaded.
	 * @since 3.3
	 */
	private static boolean fgStyleSheetLoaded= false;

	/** The Browser widget */
	private boolean fIsUsingBrowserWidget;

	private RGB fBackgroundColorRGB;
	private RGB fForegroundColorRGB;
	/**
	 * The font listener.
	 * @since 3.3
	 */
	private IPropertyChangeListener fFontListener;

	/**
	 * Holds original Javadoc input string.
	 * @since 3.4
	 */
	private String fOriginalInput;

	/**
	 * The current input element if any
	 * @since 3.4
	 */
	private BrowserInput fCurrent;

	/**
	 * Action to go back in the link history.
	 * @since 3.4
	 */
	private BackAction fBackAction;

	/**
	 * Action to go forth in the link history.
	 * @since 3.4
	 */
	private ForthAction fForthAction;

	/**
	 * Action to open the attached Javadoc.
	 * @since 3.4
	 */
	private OpenInBrowserAction fOpenBrowserAction;

	/**
	 * A selection provider providing the current
	 * Java element input of this view as selection.
	 * @since 3.4
	 */
	private ISelectionProvider fInputSelectionProvider;

	/**
	 * The Javadoc view's select all action.
	 */
	private class SelectAllAction extends Action {

		/** The control. */
		private final Control fControl;
		/** The selection provider. */
		private final SelectionProvider fSelectionProvider;

		/**
		 * Creates the action.
		 *
		 * @param control the widget
		 * @param selectionProvider the selection provider
		 */
		public SelectAllAction(Control control, SelectionProvider selectionProvider) {
			super("selectAll"); //$NON-NLS-1$

			Assert.isNotNull(control);
			Assert.isNotNull(selectionProvider);
			fControl= control;
			fSelectionProvider= selectionProvider;

			// FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
			setEnabled(!fIsUsingBrowserWidget);

			setText(InfoViewMessages.SelectAllAction_label);
			setToolTipText(InfoViewMessages.SelectAllAction_tooltip);
			setDescription(InfoViewMessages.SelectAllAction_description);

			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IAbstractTextEditorHelpContextIds.SELECT_ALL_ACTION);
		}

		/**
		 * Selects all in the view.
		 */
		@Override
		public void run() {
			if (fControl instanceof StyledText)
				((StyledText)fControl).selectAll();
			else {
				// FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
//				((Browser)fControl).selectAll();
				if (fSelectionProvider != null)
					fSelectionProvider.fireSelectionChanged();
			}
		}
	}

	/**
	 * The Javadoc view's selection provider.
	 */
	private static class SelectionProvider implements ISelectionProvider {

		/** The selection changed listeners. */
		private final ListenerList<ISelectionChangedListener> fListeners= new ListenerList<>(ListenerList.IDENTITY);
		/** The widget. */
		private final Control fControl;

		/**
		 * Creates a new selection provider.
		 *
		 * @param control	the widget
		 */
		public SelectionProvider(Control control) {
			Assert.isNotNull(control);
			fControl= control;
			if (fControl instanceof StyledText) {
				((StyledText)fControl).addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						fireSelectionChanged();
					}
				});
			} else {
				// FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
//				((Browser)fControl).addSelectionListener(new SelectionAdapter() {
//					public void widgetSelected(SelectionEvent e) {
//						fireSelectionChanged();
//					}
//				});
			}
		}

		/**
		 * Sends a selection changed event to all listeners.
		 */
		public void fireSelectionChanged() {
			ISelection selection= getSelection();
			SelectionChangedEvent event= new SelectionChangedEvent(this, selection);
			for (ISelectionChangedListener selectionChangedListener : fListeners) {
				selectionChangedListener.selectionChanged(event);
			}
		}

		/*
		 * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
		 */
		@Override
		public void addSelectionChangedListener(ISelectionChangedListener listener) {
			fListeners.add(listener);
		}

		/*
		 * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
		 */
		@Override
		public ISelection getSelection() {
			if (fControl instanceof StyledText) {
				IDocument document= new Document(((StyledText)fControl).getSelectionText());
				return new TextSelection(document, 0, document.getLength());
			} else {
				// FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
				return StructuredSelection.EMPTY;
			}
		}

		/*
		 * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
		 */
		@Override
		public void removeSelectionChangedListener(ISelectionChangedListener listener) {
			fListeners.remove(listener);
		}

		/*
		 * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection)
		 */
		@Override
		public void setSelection(ISelection selection) {
			// not supported
		}
	}

	/*
	 * @see AbstractInfoView#internalCreatePartControl(Composite)
	 */
	@Override
	protected void internalCreatePartControl(Composite parent) {
		try {
			fBrowser= new Browser(parent, SWT.NONE);
			fBrowser.setJavascriptEnabled(false);
			fIsUsingBrowserWidget= true;
			addLinkListener(fBrowser);
			fBrowser.addOpenWindowListener(event -> event.required= true);

		} catch (SWTError er) {

			/* The Browser widget throws an SWTError if it fails to
			 * instantiate properly. Application code should catch
			 * this SWTError and disable any feature requiring the
			 * Browser widget.
			 * Platform requirements for the SWT Browser widget are available
			 * from the SWT FAQ web site.
			 */

			IPreferenceStore store= JavaPlugin.getDefault().getPreferenceStore();
			boolean doNotWarn= store.getBoolean(DO_NOT_WARN_PREFERENCE_KEY);
			if (WARNING_DIALOG_ENABLED) {
				if (!doNotWarn) {
					String title= InfoViewMessages.JavadocView_error_noBrowser_title;
					String message= InfoViewMessages.JavadocView_error_noBrowser_message;
					String toggleMessage= InfoViewMessages.JavadocView_error_noBrowser_doNotWarn;
					MessageDialogWithToggle dialog= MessageDialogWithToggle.openError(parent.getShell(), title, message, toggleMessage, false, null, null);
					if (dialog.getReturnCode() == Window.OK)
						store.setValue(DO_NOT_WARN_PREFERENCE_KEY, dialog.getToggleState());
				}
			}

			fIsUsingBrowserWidget= false;
		}

		if (!fIsUsingBrowserWidget) {
			fText= new StyledText(parent, SWT.V_SCROLL | SWT.H_SCROLL);
			fText.setEditable(false);
			fPresenter= new FallbackInformationPresenter();

			fText.addControlListener(new ControlAdapter() {
				/*
				 * @see org.eclipse.swt.events.ControlAdapter#controlResized(org.eclipse.swt.events.ControlEvent)
				 */
				@Override
				public void controlResized(ControlEvent e) {
					doSetInput(fOriginalInput);
				}
			});
		}

		initStyleSheet();
		listenForFontChanges();
		getViewSite().setSelectionProvider(new SelectionProvider(getControl()));
	}

	/**
	 * Registers a listener for the Java editor font.
	 *
	 * @since 3.3
	 */
	private void listenForFontChanges() {
		fFontListener= event -> {
			if (PreferenceConstants.APPEARANCE_JAVADOC_FONT.equals(event.getProperty())) {
				fgStyleSheetLoaded= false;
				// trigger reloading, but make sure other listeners have already run, so that
				// the style sheet gets reloaded only once.
				final Display display= getSite().getPage().getWorkbenchWindow().getWorkbench().getDisplay();
				if (!display.isDisposed()) {
					display.asyncExec(() -> {
						if (!display.isDisposed()) {
							initStyleSheet();
							refresh();
						}
					});
				}
			}
		};
		JFaceResources.getFontRegistry().addListener(fFontListener);
	}

	private static void initStyleSheet() {
		if (fgStyleSheetLoaded)
			return;
		fgStyleSheetLoaded= true;
		fgStyleSheet= JavadocHover.loadStyleSheet("/JavadocViewStyleSheet.css"); //$NON-NLS-1$
	}


	/*
	 * @see AbstractInfoView#createActions()
	 */
	@Override
	protected void createActions() {
		super.createActions();
		fSelectAllAction= new SelectAllAction(getControl(), (SelectionProvider) getSelectionProvider());

		fBackAction= new BackAction();
		fBackAction.setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_BACK);
		fForthAction= new ForthAction();
		fForthAction.setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_FORWARD);

		fInputSelectionProvider= new SimpleSelectionProvider();
		fOpenBrowserAction= new OpenInBrowserAction(getSite());
		fOpenBrowserAction.setSpecialSelectionProvider(fInputSelectionProvider);
		fOpenBrowserAction.setImageDescriptor(JavaPluginImages.DESC_ELCL_OPEN_BROWSER);
		fOpenBrowserAction.setDisabledImageDescriptor(JavaPluginImages.DESC_DLCL_OPEN_BROWSER);
		fOpenBrowserAction.setActionDefinitionId(IJavaEditorActionDefinitionIds.OPEN_ATTACHED_JAVADOC);
		fInputSelectionProvider.addSelectionChangedListener(fOpenBrowserAction);

		IJavaElement input= getOrignalInput();
		StructuredSelection selection;
		if (input != null) {
			selection= new StructuredSelection(input);
		} else {
			selection= new StructuredSelection();
		}
		fInputSelectionProvider.setSelection(selection);
	}

	@Override
	protected void fillActionBars(final IActionBars actionBars) {
		super.fillActionBars(actionBars);

		actionBars.setGlobalActionHandler(ActionFactory.BACK.getId(), fBackAction);
		actionBars.setGlobalActionHandler(ActionFactory.FORWARD.getId(), fForthAction);

		fInputSelectionProvider.addSelectionChangedListener(event -> actionBars.setGlobalActionHandler(JdtActionConstants.OPEN_ATTACHED_JAVA_DOC, fOpenBrowserAction));

	}

	@Override
	protected void fillToolBar(IToolBarManager tbm) {
		tbm.add(fBackAction);
		tbm.add(fForthAction);
		tbm.add(new Separator());

		super.fillToolBar(tbm);
		tbm.add(fOpenBrowserAction);
	}

	@Override
	public void menuAboutToShow(IMenuManager menu) {
		super.menuAboutToShow(menu);

		menu.appendToGroup(IContextMenuConstants.GROUP_GOTO, fBackAction);
		menu.appendToGroup(IContextMenuConstants.GROUP_GOTO, fForthAction);

		menu.appendToGroup(IContextMenuConstants.GROUP_OPEN, fOpenBrowserAction);
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.infoviews.AbstractInfoView#getSelectAllAction()
	 * @since 3.0
	 */
	@Override
	protected IAction getSelectAllAction() {
		// FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
		if (fIsUsingBrowserWidget)
			return null;

		return fSelectAllAction;
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.infoviews.AbstractInfoView#getCopyToClipboardAction()
	 * @since 3.0
	 */
	@Override
	protected IAction getCopyToClipboardAction() {
		// FIXME: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=63022
		if (fIsUsingBrowserWidget)
			return null;

		return super.getCopyToClipboardAction();
	}

	/*
 	 * @see AbstractInfoView#setForeground(Color)
 	 */
	@Override
	protected void setForeground(Color color) {
		getControl().setForeground(color);
		fForegroundColorRGB= color.getRGB();
		refresh();
	}

	/*
	 * @see AbstractInfoView#setBackground(Color)
	 */
	@Override
	protected void setBackground(Color color) {
		getControl().setBackground(color);
		fBackgroundColorRGB= color.getRGB();
		refresh();
	}

	/**
	 * Refreshes the view.
	 *
	 * @since 3.3
	 */
	private void refresh() {
		doSetInput(computeInput(getOrignalInput()));
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.infoviews.AbstractInfoView#getBackgroundColorKey()
	 * @since 3.2
	 */
	@Override
	protected String getBackgroundColorKey() {
		return "org.eclipse.jdt.ui.Javadoc.backgroundColor";		 //$NON-NLS-1$
	}

	@Override
	protected String getForegroundColorKey() {
		return "org.eclipse.jdt.ui.Javadoc.foregroundColor";		 //$NON-NLS-1$
	}

	/*
	 * @see AbstractInfoView#internalDispose()
	 */
	@Override
	protected void internalDispose() {
		fText= null;
		fBrowser= null;
		if (fFontListener != null) {
			JFaceResources.getFontRegistry().removeListener(fFontListener);
			fFontListener= null;
		}

		if (fOpenBrowserAction != null) {
			fInputSelectionProvider.removeSelectionChangedListener(fOpenBrowserAction);
			fOpenBrowserAction= null;
		}
	}

	/*
	 * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
	 */
	@Override
	public void setFocus() {
		getControl().setFocus();
	}

	/*
	 * @see AbstractInfoView#computeInput(Object)
	 */
	@Override
	protected Object computeInput(Object input) {
		if (getControl() == null || ! (input instanceof IJavaElement))
			return null;

		IWorkbenchPart part= null;
		IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (window != null) {
			IWorkbenchPage page= window.getActivePage();
			if (page != null) {
				part= page.getActivePart();
			}
		}

		ISelection selection= null;
		if (part != null) {
			IWorkbenchPartSite site= part.getSite();
			if (site != null) {
				ISelectionProvider provider= site.getSelectionProvider();
				if (provider != null) {
					selection= provider.getSelection();
				}
			}
		}

		return computeInput(part, selection, (IJavaElement) input, new NullProgressMonitor());
	}

	@Override
	protected Object computeInput(IWorkbenchPart part, ISelection selection, IJavaElement input, IProgressMonitor monitor) {
		if (getControl() == null || input == null)
			return null;

		String javadocHtml;

		switch (input.getElementType()) {
			case IJavaElement.COMPILATION_UNIT:
				try {
					IType[] types= ((ICompilationUnit) input).getTypes();
					if (types.length == 0 && JavaModelUtil.isPackageInfo((ICompilationUnit) input)) {
						javadocHtml= getJavadocHtml(new IJavaElement[] { input.getParent() }, part, selection, monitor);
					} else {
						javadocHtml= getJavadocHtml(types, part, selection, monitor);
					}
				} catch (JavaModelException ex) {
					javadocHtml= null;
				}
				break;
			case IJavaElement.CLASS_FILE:
				if (JavaModelUtil.PACKAGE_INFO_CLASS.equals(input.getElementName())) {
					javadocHtml= getJavadocHtml(new IJavaElement[] { input.getParent() }, part, selection, monitor);
				} else if (input instanceof IModularClassFile) {
					try {
						javadocHtml= getJavadocHtml(new IJavaElement[] { ((IModularClassFile) input).getModule() }, part, selection, monitor);
					} catch (JavaModelException e) {
						return null;
					}
				} else {
					javadocHtml= getJavadocHtml(new IJavaElement[] { ((IOrdinaryClassFile) input).getType() }, part, selection, monitor);
				}
				break;
			default:
				javadocHtml= getJavadocHtml(new IJavaElement[] { input }, part, selection, monitor);
		}

		return javadocHtml;
	}

	/*
	 * @see AbstractInfoView#computeDescription(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection, org.eclipse.jdt.core.IJavaElement, org.eclipse.core.runtime.IProgressMonitor)
	 * @since 3.4
	 */
	@Override
	protected String computeDescription(IWorkbenchPart part, ISelection selection, IJavaElement inputElement, IProgressMonitor monitor) {
		return ""; //$NON-NLS-1$
	}

	/**
	 * Set input to the given input.
	 *
	 * @param input the input for the view
	 * @since 3.4
	 */
	public void setInput(BrowserInput input) {
		fCurrent= input;

		Object inputElement= input.getInputElement();
		if (inputElement instanceof IJavaElement) {
			setInput((IJavaElement) inputElement);
		} else if (inputElement instanceof URL) {
			fBrowser.setUrl(((URL) inputElement).toExternalForm());

			if (fInputSelectionProvider != null)
				fInputSelectionProvider.setSelection(new StructuredSelection(inputElement));
		}

		fForthAction.update();
		fBackAction.update();
	}

	/**
	 * {@inheritDoc}
	 *
	 * @param input a String containing the HTML to be shown in the view, or <code>null</code>
	 */
	@Override
	protected void doSetInput(Object input) {
		String javadocHtml;
		if (input instanceof String) {
			javadocHtml= (String) input;
		} else {
			StringBuilder buffer= new StringBuilder();
			HTMLPrinter.insertPageProlog(buffer, 0, fForegroundColorRGB, fBackgroundColorRGB, fgStyleSheet);
			HTMLPrinter.addPageEpilog(buffer);
			javadocHtml= buffer.toString();
		}
		fOriginalInput= javadocHtml;

		if (fInputSelectionProvider != null) {
			IJavaElement inputElement= getOrignalInput();
			StructuredSelection selection= inputElement == null ? StructuredSelection.EMPTY : new StructuredSelection(inputElement);
			fInputSelectionProvider.setSelection(selection);
		}

		if (fOpenBrowserAction != null)
			fOpenBrowserAction.setEnabled(input != null);

		if (fIsUsingBrowserWidget) {
			if (javadocHtml != null && javadocHtml.length() > 0) {
				boolean RTL= (getSite().getShell().getStyle() & SWT.RIGHT_TO_LEFT) != 0;
				if (RTL) {
					StringBuilder buffer= new StringBuilder(javadocHtml);
					HTMLPrinter.insertStyles(buffer, new String[] { "direction:rtl" } ); //$NON-NLS-1$
					javadocHtml= buffer.toString();
				}
			}
			fBrowser.setText(javadocHtml);
		} else {
			fPresentation.clear();
			Rectangle size=  fText.getClientArea();

			try {
				javadocHtml= fPresenter.updatePresentation(fText, javadocHtml, fPresentation, size.width, size.height);
			} catch (IllegalArgumentException ex) {
				// the javadoc might no longer be valid
				return;
			}
			fText.setText(javadocHtml);
			TextPresentation.applyTextPresentation(fPresentation, fText);
		}
	}

	/**
	 * Returns the Javadoc of the Java element in HTML format.
	 *
	 * @param result the Java elements for which to get the Javadoc
	 * @param activePart the active part if any
	 * @param selection the selection of the active site if any
	 * @param monitor a monitor to report progress to
	 * @return a string with the Javadoc in HTML format, or <code>null</code> if none
	 */
	private String getJavadocHtml(IJavaElement[] result, IWorkbenchPart activePart, ISelection selection, IProgressMonitor monitor) {
		StringBuilder buffer= new StringBuilder();
		int nResults= result.length;

		if (nResults == 0)
			return null;

		String base= null;
		if (nResults > 1) {
			for (IJavaElement r : result) {
				HTMLPrinter.startBulletList(buffer);
				IJavaElement curr= r;
				if (curr instanceof IMember || curr instanceof IPackageFragment || curr instanceof IPackageDeclaration || curr.getElementType() == IJavaElement.LOCAL_VARIABLE) {
					HTMLPrinter.addBullet(buffer, getInfoText(curr, null, null, false));
					HTMLPrinter.endBulletList(buffer);
				}
			}
		} else {
			IJavaElement curr= result[0];
			if (curr instanceof IPackageDeclaration || curr instanceof IPackageFragment) {
				HTMLPrinter.addSmallHeader(buffer, getInfoText(curr, null, null, true));
				buffer.append("<br>"); //$NON-NLS-1$
				Reader reader= null;
				String content= null;
				try {
					if (curr instanceof IPackageDeclaration) {
						try {
							ISourceRange nameRange= ((IPackageDeclaration) curr).getNameRange();
							if (SourceRange.isAvailable(nameRange)) {
								ITypeRoot typeRoot= (ITypeRoot) ((IPackageDeclaration) curr).getParent();
								Region hoverRegion= new Region(nameRange.getOffset(), nameRange.getLength());
								JavadocHover.addAnnotations(buffer, typeRoot.getParent(), typeRoot, hoverRegion);
							}
						} catch (JavaModelException e) {
							// no annotations this time...
						}

						content= JavadocContentAccess2.getHTMLContent((IPackageDeclaration) curr);
					} else if (curr instanceof IPackageFragment) {
						JavadocHover.addAnnotations(buffer, curr, null, null);
						content= JavadocContentAccess2.getHTMLContent((IPackageFragment) curr);
					}
				} catch (CoreException e) {
					reader= new StringReader(JavaDocLocations.handleFailedJavadocFetch(e));
				}
				IPackageFragmentRoot root= (IPackageFragmentRoot) curr.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
				try {
					boolean isBinary= root.getKind() == IPackageFragmentRoot.K_BINARY;
					if (content != null) {
						base= JavadocContentAccess2.extractBaseURL(content);
						if (base == null) {
							base= JavaDocLocations.getBaseURL(curr, isBinary);
						}
						reader= new StringReader(content);
					} else if (reader == null) {
						String explanationForMissingJavadoc= JavaDocLocations.getExplanationForMissingJavadoc(curr, root);
						if (explanationForMissingJavadoc != null) {
							reader= new StringReader(explanationForMissingJavadoc);
						}
					}
				} catch (JavaModelException e) {
					reader= new StringReader(InfoViewMessages.JavadocView_error_gettingJavadoc);
					JavaPlugin.log(e);
				}
				if (reader != null) {
					HTMLPrinter.addParagraph(buffer, reader);
				}
			} else if (curr instanceof IMember || curr instanceof ILocalVariable || curr instanceof ITypeParameter) {
				final IJavaElement element= curr;

				ITypeRoot typeRoot= null;
				Region hoverRegion= null;
				try {
					ISourceRange nameRange= ((ISourceReference) curr).getNameRange();
					if (SourceRange.isAvailable(nameRange)) {
						if (element instanceof ILocalVariable) {
							typeRoot= ((ILocalVariable) curr).getTypeRoot();
						} else if (element instanceof ITypeParameter) {
							typeRoot= ((ITypeParameter) curr).getTypeRoot();
						} else {
							typeRoot= ((IMember) curr).getTypeRoot();
						}
						hoverRegion= new Region(nameRange.getOffset(), nameRange.getLength());
					}
				} catch (JavaModelException e) {
					// no annotations this time...
				}

				String constantValue= null;
				if (element instanceof IField) {
					constantValue= computeFieldConstant(activePart, selection, (IField) element, monitor);
					if (constantValue != null)
						constantValue= HTMLPrinter.convertToHTMLContentWithWhitespace(constantValue);
				}

				String defaultValue= null;
				if (element instanceof IMethod) {
					try {
						defaultValue= JavadocHover.getAnnotationMemberDefaultValue((IMethod) element, typeRoot, hoverRegion);
						if (defaultValue != null) {
							defaultValue= HTMLPrinter.convertToHTMLContentWithWhitespace(defaultValue);
						}
					} catch (JavaModelException e) {
						// no default value
					}
				}

				HTMLPrinter.addSmallHeader(buffer, getInfoText(element, constantValue, defaultValue, true));

				if (typeRoot != null && hoverRegion != null) {
					buffer.append("<br>"); //$NON-NLS-1$
					JavadocHover.addAnnotations(buffer, curr, typeRoot, hoverRegion);
				}

				Reader reader= null;
				try {
					String content= JavadocContentAccess2.getHTMLContent(element, true);
					IPackageFragmentRoot root= (IPackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
					if (content != null) {
						IMember member;
						if (element instanceof ILocalVariable) {
							member= ((ILocalVariable) element).getDeclaringMember();
						} else if (element instanceof ITypeParameter) {
							member= ((ITypeParameter) element).getDeclaringMember();
						} else {
							member= (IMember) element;
						}
						base= JavadocContentAccess2.extractBaseURL(content);
						if (base == null) {
							base= JavaDocLocations.getBaseURL(member, member.isBinary());
						}
						reader= new StringReader(content);
					} else {
						String explanationForMissingJavadoc= JavaDocLocations.getExplanationForMissingJavadoc(element, root);
						if (explanationForMissingJavadoc != null) {
							reader= new StringReader(explanationForMissingJavadoc);
						}
					}
				} catch (CoreException ex) {
					reader= new StringReader(JavaDocLocations.handleFailedJavadocFetch(ex));
				}
				if (reader != null) {
					HTMLPrinter.addParagraph(buffer, reader);
				}

			}
		}

		if (buffer.length() == 0)
			return null;

		HTMLPrinter.insertPageProlog(buffer, 0, fForegroundColorRGB, fBackgroundColorRGB, fgStyleSheet);
		if (base != null) {
			int endHeadIdx= buffer.indexOf("</head>"); //$NON-NLS-1$
			buffer.insert(endHeadIdx, "\n<base href='" + base + "'>\n"); //$NON-NLS-1$ //$NON-NLS-2$
		}
		HTMLPrinter.addPageEpilog(buffer);
		return buffer.toString();
	}


	/**
	 * Gets the label for the given member.
	 *
	 * @param member the Java member
	 * @param constantValue the constant value if any
	 * @param defaultValue the default value of the annotation type member, if any
	 * @param allowImage <code>true</code> if the Java element image should be shown
	 * @return a string containing the member's label
	 */
	private String getInfoText(IJavaElement member, String constantValue, String defaultValue, boolean allowImage) {
		long flags= JavadocHover.getHeaderFlags(member);
		IBinding binding= JavadocHover.getHoverBinding(member, null);
		StringBuffer label;
		if (binding != null) {
			label= new StringBuffer();
			// setting haveSource to false lets the JavadocView *always* show qualified type names,
			// would need to track the source of our input to distinguish classfile/compilationUnit:
			boolean haveSource= false;
			new BindingLinkedLabelComposer(member, label, haveSource).appendBindingLabel(binding, flags);
		} else {
			label= new StringBuffer(JavaElementLinks.getElementLabel(member, flags));
		}
		if (member.getElementType() == IJavaElement.FIELD && constantValue != null) {
			label.append(constantValue);
		} else if (member.getElementType() == IJavaElement.METHOD && defaultValue != null) {
			label.append(JavadocHover.CONSTANT_VALUE_SEPARATOR);
			label.append(defaultValue);
		}
		return JavadocHover.getImageAndLabel(member, allowImage, label.toString());
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.infoviews.AbstractInfoView#isIgnoringNewInput(org.eclipse.jdt.core.IJavaElement, org.eclipse.jface.viewers.ISelection)
	 * @since 3.2
	 */
	@Override
	protected boolean isIgnoringNewInput(IJavaElement je, IWorkbenchPart part, ISelection selection) {
		if (fCurrent != null && fCurrent.getInputElement() instanceof URL)
			return false;

		if (super.isIgnoringNewInput(je, part, selection)
				&& part instanceof ITextEditor
				&& selection instanceof ITextSelection) {

			ITextEditor editor= (ITextEditor)part;
			IDocumentProvider docProvider= editor.getDocumentProvider();
			if (docProvider == null)
				return false;

			IDocument document= docProvider.getDocument(editor.getEditorInput());
			if (!(document instanceof IDocumentExtension3))
				return false;

			try {
				int offset= ((ITextSelection)selection).getOffset();
				String partition= ((IDocumentExtension3)document).getContentType(IJavaPartitions.JAVA_PARTITIONING, offset, false);
				return !IJavaPartitions.JAVA_DOC.equals(partition);
			} catch (BadPartitioningException | BadLocationException ex) {
				return false;
			}

		}
		return false;
	}

	/*
	 * @see AbstractInfoView#findSelectedJavaElement(IWorkbenchPart)
	 */
	@Override
	protected IJavaElement findSelectedJavaElement(IWorkbenchPart part, ISelection selection) {
		IJavaElement element= super.findSelectedJavaElement(part, selection);
		try {
			//update the Javadoc view when package.html is selected in project explorer view
			if (element == null && selection instanceof IStructuredSelection) {
				Object selectedElement= ((IStructuredSelection) selection).getFirstElement();
				if (selectedElement instanceof IFile) {
					IFile selectedFile= (IFile) selectedElement;
					if (JavaModelUtil.PACKAGE_HTML.equals(selectedFile.getName())) {
						element= JavaCore.create(selectedFile.getParent());
					}
				} else if (selectedElement instanceof IJarEntryResource) {
					IJarEntryResource jarEntryResource= (IJarEntryResource) selectedElement;
					if (JavaModelUtil.PACKAGE_HTML.equals(jarEntryResource.getName())) {
						Object parent= jarEntryResource.getParent();
						if (parent instanceof IJavaElement) {
							element= (IJavaElement) parent;
						}
					}

				}
			}

			if (element == null && selection instanceof ITextSelection) {
				ITextSelection textSelection= (ITextSelection) selection;
				if (part instanceof AbstractDecoratedTextEditor) {
					AbstractDecoratedTextEditor editor= (AbstractDecoratedTextEditor) part;
					IDocumentProvider documentProvider= editor.getDocumentProvider();
					if (documentProvider != null) {
						IEditorInput editorInput= editor.getEditorInput();
						IDocument document= documentProvider.getDocument(editorInput);

						if (document != null) {
							ITypedRegion typedRegion= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, textSelection.getOffset(), false);
							if (IJavaPartitions.JAVA_DOC.equals(typedRegion.getType())){
								element= TextSelectionConverter.getElementAtOffset((JavaEditor) part, textSelection);
							}
							else if (editorInput instanceof IFileEditorInput) {
								IFile file= ((IFileEditorInput) editorInput).getFile();
								//update the Javadoc view when the content of the package.html is modified in the editor
								if (JavaModelUtil.PACKAGE_HTML.equals(file.getName())) {
									element= JavaCore.create(file.getParent());
								}
							}
						}
					}
				}
			}

		} catch (JavaModelException | BadLocationException e) {
			return null;
		}
		return element;
	}

	/*
	 * @see AbstractInfoView#getControl()
	 */
	@Override
	protected Control getControl() {
		if (fIsUsingBrowserWidget)
			return fBrowser;
		else
			return fText;
	}

	/*
	 * @see org.eclipse.jdt.internal.ui.infoviews.AbstractInfoView#getHelpContextId()
	 * @since 3.1
	 */
	@Override
	protected String getHelpContextId() {
		return IJavaHelpContextIds.JAVADOC_VIEW;
	}

	/**
	 * Compute the textual representation of a 'static' 'final' field's constant initializer value.
	 *
	 * @param activePart the part that triggered the computation, or <code>null</code>
	 * @param selection the selection that references the field, or <code>null</code>
	 * @param resolvedField the field whose constant value will be computed
	 * @param monitor the progress monitor
	 *
	 * @return the textual representation of the constant, or <code>null</code> if the
	 *   field is not a constant field, the initializer value could not be computed, or
	 *   the progress monitor was cancelled
	 * @since 3.4
	 */
	private String computeFieldConstant(IWorkbenchPart activePart, ISelection selection, IField resolvedField, IProgressMonitor monitor) {

		if (!JavadocHover.isStaticFinal(resolvedField))
			return null;

		Object constantValue;

		if (selection instanceof ITextSelection && activePart instanceof JavaEditor) {
			IEditorPart editor= (IEditorPart) activePart;
			ITypeRoot activeType= JavaUI.getEditorInputTypeRoot(editor.getEditorInput());
			constantValue= getConstantValueFromActiveEditor(activeType, resolvedField, (ITextSelection) selection, monitor);
			if (constantValue == null) // fall back - e.g. when selection is inside Javadoc of the element
				constantValue= computeFieldConstantFromTypeAST(resolvedField, monitor);
		} else {
			constantValue= computeFieldConstantFromTypeAST(resolvedField, monitor);
		}

		if (constantValue != null)
			return JavadocHover.CONSTANT_VALUE_SEPARATOR + formatCompilerConstantValue(constantValue);

		return null;
	}

	/**
	 * Retrieve a constant initializer value of a field by (AST) parsing field's type.
	 *
	 * @param constantField the constant field
	 * @param monitor the progress monitor or null
	 * @return the constant value of the field, or <code>null</code> if it could not be computed
	 *   (or if the progress was cancelled).
	 * @since 3.4
	 */
	public static Object computeFieldConstantFromTypeAST(IField constantField, IProgressMonitor monitor) {
		if (monitor != null && monitor.isCanceled())
			return null;

		CompilationUnit ast= SharedASTProviderCore.getAST(constantField.getTypeRoot(), SharedASTProviderCore.WAIT_NO, monitor);
		if (ast != null) {
			try {
				if (constantField.isEnumConstant())
					return null;

				VariableDeclarationFragment fieldDecl= ASTNodeSearchUtil.getFieldDeclarationFragmentNode(constantField, ast);
				if (fieldDecl == null)
					return null;
				Expression initializer= fieldDecl.getInitializer();
				if (initializer == null)
					return null;
				return initializer.resolveConstantExpressionValue();
			} catch (JavaModelException e) {
				// ignore the exception and try the next method
			}
		}

		if (monitor != null && monitor.isCanceled())
			return null;

		ASTParser p= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
		p.setProject(constantField.getJavaProject());
		IBinding[] createBindings;
		try {
			createBindings= p.createBindings(new IJavaElement[] { constantField }, monitor);
		} catch (OperationCanceledException e) {
			return null;
		}

		if (createBindings[0] instanceof IVariableBinding)
			return ((IVariableBinding) createBindings[0]).getConstantValue();

		return null;
	}

	/**
	 * Returns the constant value for a field that is referenced by the currently active type. This
	 * method does may not run in the main UI thread.
	 *
	 * @param activeType the type that is currently active
	 * @param field the field that is being referenced (usually not declared in
	 *            <code>activeType</code>)
	 * @param selection the region in <code>activeType</code> that contains the field reference
	 * @param monitor a progress monitor
	 *
	 * @return the constant value for the given field or <code>null</code> if none
	 * @since 3.4
	 */
	private static Object getConstantValueFromActiveEditor(ITypeRoot activeType, IField field, ITextSelection selection, IProgressMonitor monitor) {
		CompilationUnit unit= SharedASTProviderCore.getAST(activeType, SharedASTProviderCore.WAIT_ACTIVE_ONLY, monitor);
		if (unit == null)
			return null;

		ASTNode node= NodeFinder.perform(unit, selection.getOffset(), selection.getLength());
		return JavadocHover.getVariableBindingConstValue(node, field);
	}

	/**
	 * Returns the string representation of the given constant value.
	 *
	 * @param constantValue the constant value
	 * @return the string representation of the given constant value.
	 * @since 3.4
	 */
	private static String formatCompilerConstantValue(Object constantValue) {
		if (constantValue instanceof String) {
			StringBuilder result= new StringBuilder();
			result.append('"');
			String stringConstant= (String)constantValue;
			if (stringConstant.length() > 80) {
				result.append(stringConstant.substring(0, 80));
				result.append(JavaElementLabels.ELLIPSIS_STRING);
			} else {
				result.append(stringConstant);
			}
			result.append('"');
			return result.toString();

		} else {
			return JavadocHover.getHexConstantValue(constantValue);
		}
	}


	/**
	 * see also org.eclipse.jdt.internal.ui.text.java.hover.JavadocHover.addLinkListener(BrowserInformationControl)
	 *
	 * Add link listener to the given browser
	 * @param browser the browser to add a listener to
	 * @since 3.4
	 */
	private void addLinkListener(Browser browser) {
		browser.addLocationListener(JavaElementLinks.createLocationListener(new JavaElementLinks.ILinkHandler() {

			@Override
			public void handleDeclarationLink(IJavaElement target) {
				try {
					JavadocHover.openDeclaration(target);
				} catch (PartInitException | JavaModelException e) {
					JavaPlugin.log(e);
				}
			}

			@Override
			public boolean handleExternalLink(final URL url, Display display) {
				if (fCurrent == null ||
						!(fCurrent.getInputElement() instanceof URL)
						|| !url.toExternalForm().equals(((URL) fCurrent.getInputElement()).toExternalForm())) {
					fCurrent= new URLBrowserInput(fCurrent, url);

					if (fBackAction != null) {
						fBackAction.update();
						fForthAction.update();
					}

					if (fInputSelectionProvider != null)
						fInputSelectionProvider.setSelection(new StructuredSelection(url));
				}

				return false;
			}

			@Override
			public void handleInlineJavadocLink(IJavaElement target) {
				JavaElementBrowserInput newInput= new JavaElementBrowserInput(fCurrent, target);
				JavadocView.this.setInput(newInput);
			}

			@Override
			public void handleJavadocViewLink(IJavaElement target) {
				handleInlineJavadocLink(target);
			}

			@Override
			public void handleTextSet() {
				IJavaElement input= getOrignalInput();
				if (input == null)
					return;

				if (fCurrent == null || !fCurrent.getInputElement().equals(input)) {
					fCurrent= new JavaElementBrowserInput(null, input);

					if (fBackAction != null) {
						fBackAction.update();
						fForthAction.update();
					}
				}
			}
		}));
	}

}
