/*******************************************************************************
 * Copyright (c) 2000, 2022 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.debug.ui.actions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointManager;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.actions.IToggleBreakpointsTargetExtension2;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageDeclaration;
import org.eclipse.jdt.core.ISourceRange;
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.Signature;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaClassPrepareBreakpoint;
import org.eclipse.jdt.debug.core.IJavaFieldVariable;
import org.eclipse.jdt.debug.core.IJavaLineBreakpoint;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.IJavaWatchpoint;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContext;
import org.eclipse.jdt.internal.corext.template.java.CompilationUnitContextType;
import org.eclipse.jdt.internal.corext.template.java.JavaContextType;
import org.eclipse.jdt.internal.debug.core.JavaDebugUtils;
import org.eclipse.jdt.internal.debug.core.breakpoints.FirstLambdaLocationLocator;
import org.eclipse.jdt.internal.debug.core.breakpoints.ValidBreakpointLocationLocator;
import org.eclipse.jdt.internal.debug.ui.BreakpointUtils;
import org.eclipse.jdt.internal.debug.ui.DebugWorkingCopyManager;
import org.eclipse.jdt.internal.debug.ui.IJDIPreferencesConstants;
import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.text.template.contentassist.TemplateEngine;
import org.eclipse.jdt.internal.ui.text.template.contentassist.TemplateProposal;
import org.eclipse.jdt.ui.IWorkingCopyManager;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IVerticalRulerInfo;
import org.eclipse.jface.text.templates.DocumentTemplateContext;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.SimpleMarkerAnnotation;

/**
 * Toggles a line breakpoint in a Java editor.
 *
 * @since 3.0
 */
public class ToggleBreakpointAdapter implements IToggleBreakpointsTargetExtension2 {

	private static final String EMPTY_STRING = ""; //$NON-NLS-1$


	/**
	 * Constructor
	 */
	public ToggleBreakpointAdapter() {
		// initialize helper in UI thread
		ActionDelegateHelper.getDefault();
	}

    /**
     * Returns the <code>IType</code> for the given selection
     * @param selection the current text selection
     * @return the <code>IType</code> for the text selection or <code>null</code>
     */
	protected static IType getType(ITextSelection selection) {
        IMember member = ActionDelegateHelper.getDefault().getCurrentMember(selection);
        IType type = null;
        if (member instanceof IType) {
            type = (IType) member;
        } else if (member != null) {
            type = member.getDeclaringType();
        }
        // bug 52385: we don't want local and anonymous types from compilation
        // unit,
        // we are getting 'not-always-correct' names for them.
        try {
            while (type != null && !type.isBinary() && type.isLocal()) {
                type = type.getDeclaringType();
            }
		} catch (JavaModelException e) {
			JDIDebugUIPlugin.log(e);
		}
        return type;
    }

    /**
     * Returns the IType associated with the <code>IJavaElement</code> passed in
     * @param element the <code>IJavaElement</code> to get the type from
     * @return the corresponding <code>IType</code> for the <code>IJavaElement</code>, or <code>null</code> if there is not one.
     * @since 3.3
     */
	protected static IType getType(IJavaElement element) {
    	switch(element.getElementType()) {
	    	case IJavaElement.FIELD: {
	    		return ((IField)element).getDeclaringType();
	    	}
	    	case IJavaElement.METHOD: {
	    		return ((IMethod)element).getDeclaringType();
	    	}
	    	case IJavaElement.TYPE: {
	    		return (IType)element;
	    	}
	    	default: {
	    		return null;
	    	}
    	}
    }

    @Override
	public void toggleLineBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
    	toggleLineBreakpoints(part, selection, false, null);
    }

    /**
     * Toggles a line breakpoint.
     * @param part the currently active workbench part
     * @param selection the current selection
     * @param bestMatch if we should make a best match or not
     */
	public static void toggleLineBreakpoints(final IWorkbenchPart part, final ISelection selection, final boolean bestMatch, final ValidBreakpointLocationLocator locator) {
        Job job = new Job("Toggle Line Breakpoint") { //$NON-NLS-1$
            @Override
			protected IStatus run(IProgressMonitor monitor) {
            	return doLineBreakpointToggle(selection, part, locator, bestMatch, monitor);
            }
        };
        job.setPriority(Job.INTERACTIVE);
        job.setSystem(true);
        job.schedule();
    }

    @Override
	public boolean canToggleLineBreakpoints(IWorkbenchPart part, ISelection selection) {
    	if (isRemote(part, selection)) {
    		return false;
    	}
        return selection instanceof ITextSelection;
    }

    @Override
	public void toggleMethodBreakpoints(final IWorkbenchPart part, final ISelection finalSelection) {
        Job job = new Job("Toggle Method Breakpoints") { //$NON-NLS-1$
            @Override
			protected IStatus run(IProgressMonitor monitor) {
                if (monitor.isCanceled()) {
                    return Status.CANCEL_STATUS;
                }
                try {
					return doToggleMethodBreakpoints(part, finalSelection, monitor);
                } catch (CoreException e) {
                    return e.getStatus();
				} finally {
					BreakpointToggleUtils.setUnsetTracepoints(false);
				}
            }
        };
        job.setPriority(Job.INTERACTIVE);
        job.setSystem(true);
        job.schedule();
    }

	public void toggleLambdaMethodBreakpoints(final IWorkbenchPart part, final ISelection finalSelection, final ValidBreakpointLocationLocator loc) {
		Job job = new Job("Toggle Lambda Method Breakpoints") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				if (monitor.isCanceled()) {
					return Status.CANCEL_STATUS;
				}
				try {
					return doToggleLambdaMethodBreakpoints(part, finalSelection, loc, monitor);
				} catch (CoreException e) {
					return e.getStatus();
				} finally {
					BreakpointToggleUtils.setUnsetTracepoints(false);
				}
			}
		};
		job.setPriority(Job.INTERACTIVE);
		job.setSystem(true);
		job.schedule();
	}

	public void toggleLambdaEntryMethodBreakpoints(final IWorkbenchPart part, final ISelection finalSelection, final String lambdaMethodName, final String lambdaMethodSignature) {
		Job job = new Job("Toggle Lambda Entry Method Breakpoints") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				if (monitor.isCanceled()) {
					return Status.CANCEL_STATUS;
				}
				try {
					return doToggleLambdaEntryMethodBreakpoints(part, finalSelection, lambdaMethodName, lambdaMethodSignature, monitor);
				} catch (CoreException e) {
					return e.getStatus();
				} finally {
					BreakpointToggleUtils.setUnsetTracepoints(false);
				}
			}
		};
		job.setPriority(Job.INTERACTIVE);
		job.setSystem(true);
		job.schedule();
	}

	static IStatus doToggleLambdaEntryMethodBreakpoints(IWorkbenchPart part, ISelection selection, String lambdaMethodName, String lambdaMethodSignature, IProgressMonitor monitor) throws CoreException {
		ITextEditor textEditor = getTextEditor(part);
		if (textEditor == null || !(selection instanceof ITextSelection)) {
			return Status.OK_STATUS;
		}
		ITextSelection textSelection = (ITextSelection) selection;
		IEditorInput editorInput = textEditor.getEditorInput();
		ITypeRoot root = getTypeRoot(editorInput);
		if (root instanceof ICompilationUnit) {
			ICompilationUnit unit = (ICompilationUnit) root;
			synchronized (unit) {
				unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
			}
			IJavaElement[] elements = unit.codeSelect(textSelection.getOffset(), textSelection.getLength());
			if (elements == null || elements.length == 0) {
				BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part);
				return Status.OK_STATUS;
			}
			IMethod method = null;
			if (elements[0] instanceof IMethod) {
				method = (IMethod) elements[0];
			} else if (elements[0].getParent() instanceof IMethod) {
				method = (IMethod) elements[0].getParent();
			}

			if (method != null) {
				doToggleMethodBreakpoint(method, lambdaMethodName, lambdaMethodSignature, part, selection, monitor);
			} else {
				BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part);
			}
		}
		return Status.OK_STATUS;
	}


	static IStatus doToggleLambdaMethodBreakpoints(IWorkbenchPart part, ISelection selection, ValidBreakpointLocationLocator loc, IProgressMonitor monitor) throws CoreException {
		ITextEditor textEditor = getTextEditor(part);
		if (textEditor == null || !(selection instanceof ITextSelection)) {
			return Status.OK_STATUS;
		}
		ITextSelection textSelection = (ITextSelection) selection;
		IEditorInput editorInput = textEditor.getEditorInput();
		ITypeRoot root = getTypeRoot(editorInput);
		if (root instanceof ICompilationUnit) {
			ICompilationUnit unit = (ICompilationUnit) root;
			synchronized (unit) {
				unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
			}
			IJavaElement[] elements = unit.codeSelect(textSelection.getOffset(), textSelection.getLength());
			if (elements == null || elements.length == 0) {
				ValidBreakpointLocationLocator locNew = new ValidBreakpointLocationLocator(loc.getCompilationUnit(), textSelection.getStartLine()
						+ 1, true, true);
				locNew.getCompilationUnit().accept(locNew);
				toggleLineBreakpoints(part, selection, false, locNew);
				return Status.OK_STATUS;
			}
			IMethod method = null;
			if (elements[0] instanceof IMethod) {
				method = (IMethod) elements[0];
			} else if (elements[0].getParent() instanceof IMethod) {
				method = (IMethod) elements[0].getParent();
			}

			if (method != null) {
				doToggleMethodBreakpoint(method, loc.getLambdaMethodName(), loc.getfLambdaMethodSignature(), part, selection, monitor);
			} else {
				ValidBreakpointLocationLocator locNew = new ValidBreakpointLocationLocator(loc.getCompilationUnit(), textSelection.getStartLine()
						+ 1, true, true);
				locNew.getCompilationUnit().accept(locNew);
				toggleLineBreakpoints(part, selection, false, locNew);
			}

		}
		return Status.OK_STATUS;
	}
	static IStatus doToggleMethodBreakpoints(IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException {
		BreakpointToggleUtils.report(null, part);
		ISelection selection = finalSelection;
		if (!(selection instanceof IStructuredSelection)) {
			selection = translateToMembers(part, selection);
		}
		boolean isInterface = isInterface(selection, part);
		if (!(selection instanceof IStructuredSelection)) {
			BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_4, part);
			return Status.OK_STATUS;
		}
		IMethod[] members = getMethods((IStructuredSelection) selection, isInterface);
		if (members.length == 0) {
			if (isInterface) {
				BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_6, part);
			} else {
				BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_9, part);
			}
			return Status.OK_STATUS;
		}
		for (IMethod member : members) {
			doToggleMethodBreakpoint(member, part, finalSelection, monitor);
		}
		return Status.OK_STATUS;
	}

	private static void doToggleMethodBreakpoint(IMethod member, IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException {
		doToggleMethodBreakpoint(member, null, null, part, finalSelection, monitor);
	}

	private static void doToggleMethodBreakpoint(IMethod member, String lambdaMethodName, String lambdaMethodSignature, IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException {
		IJavaBreakpoint breakpoint = getMethodBreakpoint(member);
		if (breakpoint != null) {
			if (BreakpointToggleUtils.isToggleTracepoints()) {
				deleteTracepoint(breakpoint, part, monitor);
				BreakpointToggleUtils.setUnsetTracepoints(false);
			} else {
				deleteBreakpoint(breakpoint, part, monitor);
			}
			return;
		}
		int start = -1;
		int end = -1;
		ISourceRange range = member.getNameRange();
		if (range != null) {
			start = range.getOffset();
			end = start + range.getLength();
		}
		Map<String, Object> attributes = new HashMap<>(10);
		BreakpointUtils.addJavaBreakpointAttributes(attributes, member);
		IType type = member.getDeclaringType();
		String mname = Optional.ofNullable(lambdaMethodName).orElse(member.getElementName());
		String signature = Optional.ofNullable(lambdaMethodSignature).orElse(member.getSignature());
		if (member.isConstructor()) {
			mname = "<init>"; //$NON-NLS-1$
			if (type.isEnum()) {
				signature = "(Ljava.lang.String;I" + signature.substring(1); //$NON-NLS-1$
			}
		}
		if (!type.isBinary() && lambdaMethodName == null) {
			signature = resolveMethodSignature(member);
			if (signature == null) {
				BreakpointToggleUtils.report(ActionMessages.ManageMethodBreakpointActionDelegate_methodNonAvailable, part);
				return;
			}
		}
		IResource resource = BreakpointUtils.getBreakpointResource(member);
		String qualifiedName = getQualifiedName(type);
		IJavaMethodBreakpoint methodBreakpoint = JDIDebugModel.createMethodBreakpoint(resource, qualifiedName, mname, signature, true, false, false, -1, start, end, 0, true, attributes);
		if (BreakpointToggleUtils.isToggleTracepoints() && finalSelection instanceof ITextSelection && part instanceof JavaEditor) {
			String pattern = getCodeTemplate((ITextSelection) finalSelection, (JavaEditor) part);
			if (pattern != null) {
				pattern = pattern.trim();
				pattern = pattern.replaceAll("\\\t", ""); //$NON-NLS-1$//$NON-NLS-2$
				methodBreakpoint.setCondition(pattern);
				methodBreakpoint.setConditionEnabled(true);
				methodBreakpoint.setConditionSuspendOnTrue(true);
			}
			BreakpointToggleUtils.setUnsetTracepoints(false);
		}
	}

	/**
	 * Performs the actual toggling of the line breakpoint
     * @param selection the current selection (from the editor or view)
     * @param part the active part
     * @param locator the locator, may be <code>null</code>
     * @param bestMatch if we should consider the best match rather than an exact match
     * @param monitor progress reporting
	 * @return the status of the toggle
	 * @since 3.8
	 */
	static IStatus doLineBreakpointToggle(ISelection selection, IWorkbenchPart part, ValidBreakpointLocationLocator locator, boolean bestMatch, IProgressMonitor monitor) {
		ITextEditor editor = getTextEditor(part);
		if (editor == null || !(selection instanceof ITextSelection)) {
			return Status.CANCEL_STATUS;
		}
		if (monitor.isCanceled()) {
			return Status.CANCEL_STATUS;
		}
		ITextSelection tsel = (ITextSelection) selection;
		if (tsel.getStartLine() < 0) {
			return Status.CANCEL_STATUS;
		}
		try {
			BreakpointToggleUtils.report(null, part);
			ISelection sel = selection;
			if (!(selection instanceof IStructuredSelection)) {
				sel = translateToMembers(part, selection);
			}
			if (!(sel instanceof IStructuredSelection)) {
				BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_3, part);
				return Status.OK_STATUS;
			}
			IMember member = (IMember) ((IStructuredSelection) sel).getFirstElement();
			IType type = null;
			if (member.getElementType() == IJavaElement.TYPE) {
				type = (IType) member;
			} else {
				type = member.getDeclaringType();
			}
			if (type == null) {
				IStatus status = new Status(IStatus.INFO, DebugUIPlugin.getUniqueIdentifier(), ActionMessages.ToggleBreakpointAdapter_ErrorMessage);
				Display.getDefault().asyncExec(() -> ErrorDialog.openError(JDIDebugUIPlugin.getShell(), ActionMessages.ToggleBreakpointAdapter_ErrorTitle, null, status));
				return status;
			}
			if (locator == null && BreakpointToggleUtils.isToggleTracepoints()) {
				CompilationUnit cUnit = parseCompilationUnit(type.getTypeRoot());
				locator = new ValidBreakpointLocationLocator(cUnit, tsel.getStartLine() + 1, true, bestMatch);
				cUnit.accept(locator);
			}
			String tname = null;
			IJavaProject project = type.getJavaProject();
			if (locator == null || (project != null && !project.isOnClasspath(type))) {
				tname = createQualifiedTypeName(type);
			} else {
				tname = locator.getFullyQualifiedTypeName();
			}
			if (tname == null) {
				return Status.CANCEL_STATUS;
			}
			IResource resource = BreakpointUtils.getBreakpointResource(type);
			int lnumber = locator == null ? tsel.getStartLine() + 1 : locator.getLineLocation();
			IJavaLineBreakpoint existingBreakpoint = JDIDebugModel.lineBreakpointExists(resource, tname, lnumber);
			if (existingBreakpoint != null) {
				if (BreakpointToggleUtils.isToggleTracepoints()) {
					deleteTracepoint(existingBreakpoint, editor, monitor);
					BreakpointToggleUtils.setUnsetTracepoints(false);
				} else {
					deleteBreakpoint(existingBreakpoint, editor, monitor);
				}
				return Status.OK_STATUS;
			}
			Map<String, Object> attributes = new HashMap<>(10);
			IDocumentProvider documentProvider = editor.getDocumentProvider();
			if (documentProvider == null) {
				return Status.CANCEL_STATUS;
			}
			IDocument document = documentProvider.getDocument(editor.getEditorInput());
			int charstart = -1, charend = -1;
			try {
				IRegion line = document.getLineInformation(lnumber - 1);
				charstart = line.getOffset();
				charend = charstart + line.getLength();
			} catch (BadLocationException ble) {
				JDIDebugUIPlugin.log(ble);
			}
			BreakpointUtils.addJavaBreakpointAttributes(attributes, type);
			IJavaLineBreakpoint breakpoint = JDIDebugModel.createLineBreakpoint(resource, tname, lnumber, charstart, charend, 0, true, attributes);
			if (BreakpointToggleUtils.isToggleTracepoints() && selection instanceof ITextSelection && part instanceof JavaEditor) {
				String pattern = getCodeTemplate((ITextSelection) selection, (JavaEditor) part);
				if (pattern != null) {
					pattern = pattern.trim();
					pattern = pattern.replaceAll("\\\t", ""); //$NON-NLS-1$//$NON-NLS-2$
					breakpoint.setCondition(pattern);
					breakpoint.setConditionEnabled(true);
					breakpoint.setConditionSuspendOnTrue(true);
				}

				BreakpointToggleUtils.setUnsetTracepoints(false);
			}
			if (locator == null) {
				new BreakpointLocationVerifierJob(document, parseCompilationUnit(type.getTypeRoot()), breakpoint, lnumber, tname, type, editor, bestMatch).schedule();
			}
			if (BreakpointToggleUtils.isToggleTracepoints()) {
				BreakpointToggleUtils.setUnsetTracepoints(false);
			}
		} catch (CoreException ce) {
			return ce.getStatus();
		} finally {
			BreakpointToggleUtils.setUnsetTracepoints(false);
		}
        return Status.OK_STATUS;
    }

    /**
     * Toggles a class load breakpoint
     * @param part the part
     * @param selection the current selection
     * @since 3.3
     */
	public static void toggleClassBreakpoints(final IWorkbenchPart part, final ISelection selection) {
    	Job job = new Job("Toggle Class Load Breakpoints") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				if (monitor.isCanceled()) {
                    return Status.CANCEL_STATUS;
                }
                try {
					return doToggleClassBreakpoints(part, selection, monitor);
				} catch (CoreException e) {
					return e.getStatus();
				}
			}
    	};
    	job.setPriority(Job.INTERACTIVE);
    	job.setSystem(true);
    	job.schedule();
    }

	static IStatus doToggleClassBreakpoints(IWorkbenchPart part, ISelection selection, IProgressMonitor monitor) throws CoreException {
		BreakpointToggleUtils.report(null, part);
		ISelection sel = selection;
		if (!(selection instanceof IStructuredSelection)) {
			sel = translateToMembers(part, selection);
		}
		if (isInterface(sel, part)) {
			BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_1, part);
			return Status.OK_STATUS;
		}
		if (!(sel instanceof IStructuredSelection)) {
			BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_0, part);
			return Status.OK_STATUS;
		}
		IMember member = (IMember) ((IStructuredSelection) sel).getFirstElement();
		IType type = (IType) member;
		IJavaBreakpoint existing = getClassLoadBreakpoint(type);
		if (existing != null) {
			deleteBreakpoint(existing, part, monitor);
			return Status.OK_STATUS;
		}
		HashMap<String, Object> map = new HashMap<>(10);
		BreakpointUtils.addJavaBreakpointAttributes(map, type);
		ISourceRange range = type.getNameRange();
		int start = -1;
		int end = -1;
		if (range != null) {
			start = range.getOffset();
			end = start + range.getLength();
		}
		IResource resource = BreakpointUtils.getBreakpointResource(member);
		String qualifiedName = getQualifiedName(type);
		JDIDebugModel.createClassPrepareBreakpoint(resource, qualifiedName, IJavaClassPrepareBreakpoint.TYPE_CLASS, start, end, true, map);
		return Status.OK_STATUS;
	}

    /**
     * Returns the class load breakpoint for the specified type or null if none found
     * @param type the type to search for a class load breakpoint for
     * @return the existing class load breakpoint, or null if none
     * @throws CoreException
     * @since 3.3
     */
	protected static IJavaBreakpoint getClassLoadBreakpoint(IType type) throws CoreException {
    	IBreakpoint[] breakpoints = DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(JDIDebugModel.getPluginIdentifier());
    	for (int i = 0; i < breakpoints.length; i++) {
			IJavaBreakpoint breakpoint= (IJavaBreakpoint)breakpoints[i];
			if (breakpoint instanceof IJavaClassPrepareBreakpoint && getQualifiedName(type).equals(breakpoint.getTypeName())) {
				return breakpoint;
			}
		}
		return null;
    }

    /**
     * Returns the binary name for the {@link IType} derived from its {@link ITypeBinding}.
     * <br><br>
     * If the {@link ITypeBinding} cannot be derived this method falls back to calling
     * {@link #createQualifiedTypeName(IType)} to try and compose the type name.
     * @param type
     * @return the binary name for the given {@link IType}
     * @since 3.6
     */
	static String getQualifiedName(IType type) throws JavaModelException {
    	IJavaProject project = type.getJavaProject();
		if (project == null || !project.isOnClasspath(type) || !needsBindings(type)) {
			return createQualifiedTypeName(type);
		}
		CompilationUnit cuNode = parseCompilationUnit(type.getTypeRoot());
		ISourceRange nameRange = type.getNameRange();
		if (cuNode == null || !SourceRange.isAvailable(nameRange)) {
			return createQualifiedTypeName(type);
		}
		ASTNode node = NodeFinder.perform(cuNode, nameRange);
		if (!(node instanceof SimpleName)) {
			return createQualifiedTypeName(type);
		}
		IBinding binding;
		if (node.getLocationInParent() == SimpleType.NAME_PROPERTY && node.getParent().getLocationInParent() == ClassInstanceCreation.TYPE_PROPERTY) {
			binding = ((ClassInstanceCreation) node.getParent().getParent()).resolveTypeBinding();
		} else {
			binding = ((SimpleName) node).resolveBinding();
		}
		if (binding instanceof ITypeBinding) {
			String name = ((ITypeBinding) binding).getBinaryName();
			if (name != null) {
				return name;
			}
		}
		return createQualifiedTypeName(type);
    }

    /**
     * Checks if the type or any of its enclosing types are local types.
     * @param type
     * @return <code>true</code> if the type or a parent type are a local type
     * @throws JavaModelException
     * @since 3.6
     */
	static boolean needsBindings(IType type) throws JavaModelException {
    	if(type.isMember()) {
    		if(type.isLocal() && !type.isAnonymous()) {
    			return true;
    		}
    		IJavaElement parent = type.getParent();
    		IType ptype = null;
    		while(parent != null) {
    			if(parent.getElementType() == IJavaElement.TYPE) {
    				ptype = (IType) parent;
    				if(ptype.isLocal() && !ptype.isAnonymous()) {
    					return true;
    				}
    			}
    			parent = parent.getParent();
    		}
    	}
    	return false;
    }

    /**
     * Returns the package qualified name, while accounting for the fact that a source file might
     * not have a project
     * @param type the type to ensure the package qualified name is created for
     * @return the package qualified name
     * @since 3.3
     */
	static String createQualifiedTypeName(IType type) {
    	String tname = pruneAnonymous(type);
    	try {
    		String packName = null;
    		if (type.isBinary()) {
    			packName = type.getPackageFragment().getElementName();
    		} else {
    			IPackageDeclaration[] pd = type.getCompilationUnit().getPackageDeclarations();
				if(pd.length > 0) {
					packName = pd[0].getElementName();
				}
    		}
			if(packName != null && !packName.equals(EMPTY_STRING)) {
				tname =  packName+"."+tname; //$NON-NLS-1$
			}
    	}
    	catch (JavaModelException e) {}
    	return tname;
    }

    /**
     * Prunes out all naming occurrences of anonymous inner types, since these types have no names
     * and cannot be derived visiting an AST (no positive type name matching while visiting ASTs)
     * @param type
     * @return the compiled type name from the given {@link IType} with all occurrences of anonymous inner types removed
     * @since 3.4
     */
	private static String pruneAnonymous(IType type) {
    	StringBuilder buffer = new StringBuilder();
    	IJavaElement parent = type;
    	while(parent != null) {
    		if(parent.getElementType() == IJavaElement.TYPE){
    			IType atype = (IType) parent;
    			try {
	    			if(!atype.isAnonymous()) {
	    				if(buffer.length() > 0) {
	    					buffer.insert(0, '$');
	    				}
	    				buffer.insert(0, atype.getElementName());
	    			}
    			}
    			catch(JavaModelException jme) {}
    		}
    		parent = parent.getParent();
    	}
    	return buffer.toString();
    }

    /**
     * gets the <code>IJavaElement</code> from the editor input
     * @param input the current editor input
     * @return the corresponding <code>IJavaElement</code>
     * @since 3.3
     */
	private static IJavaElement getJavaElement(IEditorInput input) {
    	IJavaElement je = JavaUI.getEditorInputJavaElement(input);
    	if(je != null) {
    		return je;
    	}
    	//try to get from the working copy manager
    	return DebugWorkingCopyManager.getWorkingCopy(input, false);
    }

    @Override
	public boolean canToggleMethodBreakpoints(IWorkbenchPart part, ISelection selection) {
    	if (isRemote(part, selection)) {
    		return false;
    	}
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection ss = (IStructuredSelection) selection;
            return getMethods(ss, isInterface(selection, part)).length > 0;
        }
        return (selection instanceof ITextSelection) && isMethod((ITextSelection) selection, part);
    }

	/**
	 * Returns whether the given part/selection is remote (viewing a repository)
	 *
	 * @param part
	 * @param selection
	 * @return
	 */
	protected static boolean isRemote(IWorkbenchPart part, ISelection selection) {
    	if (selection instanceof IStructuredSelection) {
			IStructuredSelection ss = (IStructuredSelection) selection;
			Object element = ss.getFirstElement();
			if(element instanceof IMember) {
				IMember member = (IMember) element;
				return !member.getJavaProject().getProject().exists();
			}
		}
		ITextEditor editor = getTextEditor(part);
    	if (editor != null) {
    		IEditorInput input = editor.getEditorInput();
    		Object adapter = Platform.getAdapterManager().getAdapter(input, "org.eclipse.team.core.history.IFileRevision"); //$NON-NLS-1$
    		return adapter != null;
    	}
    	return false;
    }

    /**
     * Returns the text editor associated with the given part or <code>null</code>
     * if none. In case of a multi-page editor, this method should be used to retrieve
     * the correct editor to perform the breakpoint operation on.
     *
     * @param part workbench part
     * @return text editor part or <code>null</code>
     */
	protected static ITextEditor getTextEditor(IWorkbenchPart part) {
    	if (part instanceof ITextEditor) {
    		return (ITextEditor) part;
    	}
    	return part.getAdapter(ITextEditor.class);
    }

    /**
     * Returns the methods from the selection, or an empty array
     * @param selection the selection to get the methods from
     * @return an array of the methods from the selection or an empty array
     */
	protected static IMethod[] getMethods(IStructuredSelection selection, boolean isInterace) {
        if (selection.isEmpty()) {
            return new IMethod[0];
        }
        List<IMethod> methods = new ArrayList<>(selection.size());
        Iterator<?> iterator = selection.iterator();
        while (iterator.hasNext()) {
            Object thing = iterator.next();
            try {
                if (thing instanceof IMethod) {
                	IMethod method = (IMethod) thing;
                	if(isInterace){
                		if (Flags.isDefaultMethod(method.getFlags()) || Flags.isStatic(method.getFlags())) {
							methods.add(method);
						}
                	}
                	else if (!Flags.isAbstract(method.getFlags())) {
                		methods.add(method);
                	}
                }
            }
            catch (JavaModelException e) {}
        }
        return methods.toArray(new IMethod[methods.size()]);
    }

    /**
     * Returns the methods from the selection, or an empty array
     * @param selection the selection to get the methods from
     * @return an array of the methods from the selection or an empty array
     */
	protected static IMethod[] getInterfaceMethods(IStructuredSelection selection) {
        if (selection.isEmpty()) {
            return new IMethod[0];
        }
        List<IMethod> methods = new ArrayList<>(selection.size());
        Iterator<?> iterator = selection.iterator();
        while (iterator.hasNext()) {
            Object thing = iterator.next();
            try {
                if (thing instanceof IMethod) {
                	IMethod method = (IMethod) thing;
                	if (Flags.isDefaultMethod(method.getFlags())) {
                		methods.add(method);
                	}
                }
            }
            catch (JavaModelException e) {}
        }
        return methods.toArray(new IMethod[methods.size()]);
    }

     /**
     * Returns if the text selection is a valid method or not
     * @param selection the text selection
     * @param part the associated workbench part
     * @return true if the selection is a valid method, false otherwise
     */
	private static boolean isMethod(ITextSelection selection, IWorkbenchPart part) {
		ITextEditor editor = getTextEditor(part);
		if (editor != null) {
			IJavaElement element = getJavaElement(editor.getEditorInput());
			if (element != null) {
				try {
					if (element instanceof ICompilationUnit) {
						element = ((ICompilationUnit) element).getElementAt(selection.getOffset());
					} else if (element instanceof IClassFile) {
						element = ((IClassFile) element).getElementAt(selection.getOffset());
					}
					if (element != null && element.getElementType() == IJavaElement.METHOD) {
						IMethod method = (IMethod) element;
						if (method.getDeclaringType().isAnonymous()) {
							return false;
						}
						return true;
					}

				}
				catch (JavaModelException e) {
					return false;
				}
			}
		}
		return false;
	}

    /**
     * Returns a list of <code>IField</code> and <code>IJavaFieldVariable</code> in the given selection.
     * When an <code>IField</code> can be resolved for an <code>IJavaFieldVariable</code>, it is
     * returned in favour of the variable.
     *
     * @param selection
     * @return list of <code>IField</code> and <code>IJavaFieldVariable</code>, possibly empty
     * @throws CoreException
     */
	protected static List<Object> getFields(IStructuredSelection selection) throws CoreException {
        if (selection.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<Object> fields = new ArrayList<>(selection.size());
        Iterator<?> iterator = selection.iterator();
        while (iterator.hasNext()) {
            Object thing = iterator.next();
            if (thing instanceof IField) {
                fields.add(thing);
            } else if (thing instanceof IJavaFieldVariable) {
                IField field = getField((IJavaFieldVariable) thing);
                if (field == null) {
                	fields.add(thing);
                } else {
                    fields.add(field);
                }
            }
        }
        return fields;
    }

    /**
     * Returns if the structured selection is itself or is part of an interface
     * @param selection the current selection
     * @return true if the selection is part of an interface, false otherwise
     * @since 3.2
     */
	private static boolean isInterface(ISelection selection, IWorkbenchPart part) {
		try {
			ISelection sel = selection;
			if(!(sel instanceof IStructuredSelection)) {
				sel = translateToMembers(part, selection);
			}
			if(sel instanceof IStructuredSelection) {
				Object obj = ((IStructuredSelection)sel).getFirstElement();
				if(obj instanceof IMember) {
					IMember member = (IMember) ((IStructuredSelection)sel).getFirstElement();
					if(member.getElementType() == IJavaElement.TYPE) {
						return ((IType)member).isInterface();
					}
					IType type = member.getDeclaringType();
					return type != null && type.isInterface();
				}
				else if(obj instanceof IJavaFieldVariable) {
					IJavaFieldVariable var = (IJavaFieldVariable) obj;
					IType type = JavaDebugUtils.resolveType(var.getDeclaringType());
					return type != null && type.isInterface();
				}
			}
		}
		catch (CoreException e1) {}
    	return false;
    }

    /**
     * Returns if the text selection is a field selection or not
     * @param selection the text selection
     * @param part the associated workbench part
     * @return true if the text selection is a valid field for a watchpoint, false otherwise
     * @since 3.3
     */
	private static boolean isField(ITextSelection selection, IWorkbenchPart part) {
		ITextEditor editor = getTextEditor(part);
    	if(editor != null) {
			IJavaElement element = getJavaElement(editor.getEditorInput());
    		if(element != null) {
    			try {
	    			if(element instanceof ICompilationUnit) {
						element = ((ICompilationUnit) element).getElementAt(selection.getOffset());
	    			}
	    			else if(element instanceof IClassFile) {
	    				element = ((IClassFile) element).getElementAt(selection.getOffset());
	    			}
	    			return element != null && element.getElementType() == IJavaElement.FIELD;
				}
    			catch (JavaModelException e) {return false;}
    		}
    	}
    	return false;
    }


    /**
     * Determines if the selection is a field or not
     * @param selection the current selection
     * @return true if the selection is a field false otherwise
     */
	private static boolean isFields(IStructuredSelection selection) {
		if (selection.isEmpty()) {
			return false;
        }
		try {
			Iterator<?> iterator = selection.iterator();
			while (iterator.hasNext()) {
				Object thing = iterator.next();
				if (thing instanceof IField) {
					int flags = ((IField) thing).getFlags();
					return !Flags.isFinal(flags);
				} else if (thing instanceof IJavaFieldVariable) {
					IJavaFieldVariable fv = (IJavaFieldVariable) thing;
					return !fv.isFinal();
				}
			}
		} catch (JavaModelException | DebugException e) {
			return false;
		}
        return false;
    }

    @Override
	public void toggleWatchpoints(final IWorkbenchPart part, final ISelection finalSelection) {
        Job job = new Job("Toggle Watchpoints") { //$NON-NLS-1$
            @Override
			protected IStatus run(IProgressMonitor monitor) {
                if (monitor.isCanceled()) {
                    return Status.CANCEL_STATUS;
                }
				try {
					return doToggleWatchpoints(part, finalSelection, monitor);
				} catch (CoreException e) {
					return e.getStatus();
				}
            }
        };
        job.setPriority(Job.INTERACTIVE);
        job.setSystem(true);
        job.schedule();
    }

	static IStatus doToggleWatchpoints(IWorkbenchPart part, ISelection finalSelection, IProgressMonitor monitor) throws CoreException {
		BreakpointToggleUtils.report(null, part);
		ISelection selection = finalSelection;
		if (!(selection instanceof IStructuredSelection)) {
			selection = translateToMembers(part, finalSelection);
		}
		if (isInterface(selection, part)) {
			BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_5, part);
			return Status.OK_STATUS;
		}
		boolean allowed = false;
		if (!(selection instanceof IStructuredSelection)) {
			BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_2, part);
			return Status.OK_STATUS;
		}
		List<Object> fields = getFields((IStructuredSelection) selection);
		if (fields.isEmpty()) {
			BreakpointToggleUtils.report(ActionMessages.ToggleBreakpointAdapter_10, part);
			return Status.OK_STATUS;
		}

		IField javaField = null;
		String typeName = null;
		String fieldName = null;

		for (Object element : fields) {
			if (element instanceof IField) {
				javaField = (IField) element;
				IType type = javaField.getDeclaringType();
				typeName = getQualifiedName(type);
				fieldName = javaField.getElementName();
				int f = javaField.getFlags();
				boolean fin = Flags.isFinal(f);
				if (fin) {
					fin = javaField.getConstant() != null; // watch point is allowed if no constant value
				}
				allowed = !fin;
			} else if (element instanceof IJavaFieldVariable) {
				IJavaFieldVariable var = (IJavaFieldVariable) element;
				typeName = var.getDeclaringType().getName();
				fieldName = var.getName();
				boolean fin = var.isFinal();
				if (fin) {
					fin = javaField.getConstant() != null; // watch point is allowed if no constant value
				}
				allowed = !fin;
			}
			IJavaBreakpoint breakpoint = getWatchpoint(typeName, fieldName);
			if (breakpoint != null) {
				deleteBreakpoint(breakpoint, part, monitor);
				continue;
			}
			if (!allowed) {
				return doLineBreakpointToggle(finalSelection, part, null, true, monitor);
			}
			int start = -1;
			int end = -1;
			Map<String, Object> attributes = new HashMap<>(10);
			IResource resource;
			if (javaField == null) {
				resource = ResourcesPlugin.getWorkspace().getRoot();
			} else {
				IType type = javaField.getDeclaringType();
				ISourceRange range = javaField.getNameRange();
				if (range != null) {
					start = range.getOffset();
					end = start + range.getLength();
				}
				BreakpointUtils.addJavaBreakpointAttributes(attributes, javaField);
				resource = BreakpointUtils.getBreakpointResource(type);
			}
			JDIDebugModel.createWatchpoint(resource, typeName, fieldName, -1, start, end, 0, true, attributes);
		}
		return Status.OK_STATUS;
	}

	/**
	 * Returns any existing watchpoint for the given field, or <code>null</code> if none.
	 *
     * @param typeName fully qualified type name on which watchpoint may exist
     * @param fieldName field name
	 * @return any existing watchpoint for the given field, or <code>null</code> if none
	 * @throws CoreException
	 */
	private static IJavaWatchpoint getWatchpoint(String typeName, String fieldName) throws CoreException {
        IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
        IBreakpoint[] breakpoints = breakpointManager.getBreakpoints(JDIDebugModel.getPluginIdentifier());
        for (int i = 0; i < breakpoints.length; i++) {
            IBreakpoint breakpoint = breakpoints[i];
            if (breakpoint instanceof IJavaWatchpoint) {
                IJavaWatchpoint watchpoint = (IJavaWatchpoint) breakpoint;
                if (typeName.equals(watchpoint.getTypeName()) && fieldName.equals(watchpoint.getFieldName())) {
                    return watchpoint;
                }
            }
        }
        return null;
    }

    /**
     * Returns the resolved signature of the given method
     * @param method method to resolve
     * @return the resolved method signature or <code>null</code> if none
     * @throws JavaModelException
     * @since 3.4
     */
    public static String resolveMethodSignature(IMethod method) throws JavaModelException {
    	String signature = method.getSignature();
        String[] parameterTypes = Signature.getParameterTypes(signature);
        int length = parameterTypes.length;
        String[] resolvedParameterTypes = new String[length];
        for (int i = 0; i < length; i++) {
            resolvedParameterTypes[i] = resolveTypeSignature(method, parameterTypes[i]);
            if (resolvedParameterTypes[i] == null) {
                return null;
            }
        }
        String resolvedReturnType = resolveTypeSignature(method, Signature.getReturnType(signature));
        if (resolvedReturnType == null) {
            return null;
        }
        return Signature.createMethodSignature(resolvedParameterTypes, resolvedReturnType);
    }

    /**
     * Returns the resolved type signature for the given signature in the given
     * method, or <code>null</code> if unable to resolve.
     *
     * @param method method containing the type signature
     * @param typeSignature the type signature to resolve
     * @return the resolved type signature
     * @throws JavaModelException
     */
    private static String resolveTypeSignature(IMethod method, String typeSignature) throws JavaModelException {
        int count = Signature.getArrayCount(typeSignature);
        String elementTypeSignature = Signature.getElementType(typeSignature);
        if (elementTypeSignature.length() == 1) {
            // no need to resolve primitive types
            return typeSignature;
        }
        String elementTypeName = Signature.toString(elementTypeSignature);
        IType type = method.getDeclaringType();
        String[][] resolvedElementTypeNames = type.resolveType(elementTypeName);
        if (resolvedElementTypeNames == null || resolvedElementTypeNames.length != 1) {
        	// check if type parameter
        	ITypeParameter typeParameter = method.getTypeParameter(elementTypeName);
        	if (!typeParameter.exists()) {
        		typeParameter = type.getTypeParameter(elementTypeName);
        	}
        	if (typeParameter.exists()) {
				String[] bounds = typeParameter.getBounds();
				if (bounds.length == 0) {
					return "Ljava/lang/Object;"; //$NON-NLS-1$
				}
				String bound = Signature.createTypeSignature(bounds[0], false);
				return Signature.createArraySignature(resolveTypeSignature(method, bound), count);
    		}
            // the type name cannot be resolved
            return null;
        }

        String[] types = resolvedElementTypeNames[0];
        types[1] = types[1].replace('.', '$');

        String resolvedElementTypeName = Signature.toQualifiedName(types);
		String resolvedElementTypeSignature = EMPTY_STRING;
		if (types[0].equals(EMPTY_STRING)) {
        	resolvedElementTypeName = resolvedElementTypeName.substring(1);
        	resolvedElementTypeSignature = Signature.createTypeSignature(resolvedElementTypeName, true);
        }
        else {
        	resolvedElementTypeSignature = Signature.createTypeSignature(resolvedElementTypeName, true).replace('.', '/');
        }

        return Signature.createArraySignature(resolvedElementTypeSignature, count);
    }

    /**
     * Returns the resource associated with the specified editor part
     * @param editor the currently active editor part
     * @return the corresponding <code>IResource</code> from the editor part
     */
    protected static IResource getResource(IEditorPart editor) {
        IEditorInput editorInput = editor.getEditorInput();
        IResource resource = editorInput.getAdapter(IFile.class);
        if (resource == null) {
            resource = ResourcesPlugin.getWorkspace().getRoot();
        }
        return resource;
    }

    /**
     * Returns a handle to the specified method or <code>null</code> if none.
     *
     * @param editorPart
     *            the editor containing the method
     * @param typeName
     * @param methodName
     * @param signature
     * @return handle or <code>null</code>
     */
	protected static IMethod getMethodHandle(IEditorPart editorPart, String typeName, String methodName, String signature) throws CoreException {
        IJavaElement element = editorPart.getEditorInput().getAdapter(IJavaElement.class);
        IType type = null;
        if (element instanceof ICompilationUnit) {
            IType[] types = ((ICompilationUnit) element).getAllTypes();
            for (int i = 0; i < types.length; i++) {
                if (types[i].getFullyQualifiedName().equals(typeName)) {
                    type = types[i];
                    break;
                }
            }
        } else if (element instanceof IOrdinaryClassFile) {
			type = ((IOrdinaryClassFile) element).getType();
        }
        if (type != null) {
            String[] sigs = Signature.getParameterTypes(signature);
            return type.getMethod(methodName, sigs);
        }
        return null;
    }

    /**
     * Returns the <code>IJavaBreakpoint</code> from the specified <code>IMember</code>
     * @param element the element to get the breakpoint from
     * @return the current breakpoint from the element or <code>null</code>
     */
	@SuppressWarnings("restriction")
	protected static IJavaBreakpoint getMethodBreakpoint(IMember element) {
        IBreakpointManager breakpointManager = DebugPlugin.getDefault().getBreakpointManager();
        IBreakpoint[] breakpoints = breakpointManager.getBreakpoints(JDIDebugModel.getPluginIdentifier());
		if (!(element instanceof IMethod)) {
			return null;
        }
		IMethod method = (IMethod) element;
		for (IBreakpoint breakpoint : breakpoints) {
			if (!(breakpoint instanceof IJavaMethodBreakpoint)) {
				continue;
			}
			IJavaMethodBreakpoint methodBreakpoint = (IJavaMethodBreakpoint) breakpoint;
			IMember container = null;
			try {
				container = BreakpointUtils.getMember(methodBreakpoint);
			} catch (CoreException e) {
				JDIDebugUIPlugin.log(e);
				return null;
			}
			if (container == null) {
				try {
					if (method.getDeclaringType().getFullyQualifiedName().equals(methodBreakpoint.getTypeName())
							&& method.getElementName().equals(methodBreakpoint.getMethodName())
							&& methodBreakpoint.getMethodSignature().equals(resolveMethodSignature(method))) {
						return methodBreakpoint;
					}
				} catch (CoreException e) {
					JDIDebugUIPlugin.log(e);
				}
			} else {
				if (container instanceof IMethod) {
					if (method instanceof org.eclipse.jdt.internal.core.LambdaMethod) {
						return null;
					}
					if (method.getDeclaringType().getFullyQualifiedName().equals(container.getDeclaringType().getFullyQualifiedName())) {
						if (method.isSimilar((IMethod) container)) {
							return methodBreakpoint;
						}
					}
				}
			}
		}
        return null;
    }

    /**
     * Returns the compilation unit from the editor
     * @param editor the editor to get the compilation unit from
     * @return the compilation unit or <code>null</code>
     */
	protected static CompilationUnit parseCompilationUnit(ITextEditor editor) {
        return parseCompilationUnit(getTypeRoot(editor.getEditorInput()));
    }

    /**
	 * Parses the {@link ITypeRoot}.
	 *
	 * @param root
	 *            the root
	 * @return the parsed {@link CompilationUnit} or {@code null}
	 */
	static CompilationUnit parseCompilationUnit(ITypeRoot root) {
    	if(root != null) {
			return SharedASTProviderCore.getAST(root, SharedASTProviderCore.WAIT_YES, null);
        }
        return null;
    }

    @Override
	public boolean canToggleWatchpoints(IWorkbenchPart part, ISelection selection) {
    	if (isRemote(part, selection)) {
    		return false;
    	}
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection ss = (IStructuredSelection) selection;
            return isFields(ss);
        }
        return (selection instanceof ITextSelection) && isField((ITextSelection) selection, part);
    }

    /**
     * Returns a selection of the member in the given text selection, or the
     * original selection if none.
     *
     * @param part
     * @param selection
     * @return a structured selection of the member in the given text selection,
     *         or the original selection if none
     * @exception CoreException
     *                if an exception occurs
     */
	protected static ISelection translateToMembers(IWorkbenchPart part, ISelection selection) throws CoreException {
    	ITextEditor textEditor = getTextEditor(part);
		if (textEditor == null || !(selection instanceof ITextSelection)) {
			return selection;
        }
		ITextSelection textSelection = (ITextSelection) selection;
		IEditorInput editorInput = textEditor.getEditorInput();
		IDocumentProvider documentProvider = textEditor.getDocumentProvider();
		if (documentProvider == null) {
			throw new CoreException(Status.CANCEL_STATUS);
		}
		IDocument document = documentProvider.getDocument(editorInput);
		int offset = textSelection.getOffset();
		if (document != null) {
			try {
				IRegion region = document.getLineInformationOfOffset(offset);
				int end = region.getOffset() + region.getLength();
				while (Character.isWhitespace(document.getChar(offset)) && offset < end) {
					offset++;
				}
			} catch (BadLocationException e) {
			}
		}
		IMember m = null;
		ITypeRoot root = getTypeRoot(editorInput);
		if (root instanceof ICompilationUnit) {
			ICompilationUnit unit = (ICompilationUnit) root;
			synchronized (unit) {
				unit.reconcile(ICompilationUnit.NO_AST, false, null, null);
			}
		}
		if (root != null) {
			IJavaElement e = root.getElementAt(offset);
			if (e instanceof IMember) {
				m = (IMember) e;
			}
			// Class breakpoint should be created if the offset was at the record component in the record definition
			if (m != null && m.getParent() instanceof IType && ((IType) m.getParent()).isRecord()) {
				if (m instanceof IField) {
					IField field = (IField)m;
					if (field.isRecordComponent()) {
						m = (IMember) field.getParent();
					}
				}
			}
		}
		if (m != null) {
			return new StructuredSelection(m);
		}
        return selection;
    }

    /**
     * Returns the {@link ITypeRoot} for the given {@link IEditorInput}
     * @param input
     * @return the type root or <code>null</code> if one cannot be derived
	 * @since 3.4
     */
	private static ITypeRoot getTypeRoot(IEditorInput input) {
		ITypeRoot root = input.getAdapter(IClassFile.class);
    	if(root == null) {
    		 IWorkingCopyManager manager = JavaUI.getWorkingCopyManager();
             root = manager.getWorkingCopy(input);
    	}
    	if(root == null) {
    		root = DebugWorkingCopyManager.getWorkingCopy(input, false);
    	}
    	return root;
    }

    /**
     * Return the associated IField (Java model) for the given
     * IJavaFieldVariable (JDI model)
     */
	private static IField getField(IJavaFieldVariable variable) throws CoreException {
        String varName = null;
        try {
            varName = variable.getName();
        } catch (DebugException x) {
            JDIDebugUIPlugin.log(x);
            return null;
        }
        IField field;
        IJavaType declaringType = variable.getDeclaringType();
        IType type = JavaDebugUtils.resolveType(declaringType);
        if (type != null) {
            field = type.getField(varName);
            if (field.exists()) {
                return field;
            }
        }
        return null;
    }

    @Override
	public void toggleBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
		ISelection sel = translateToMembers(part, selection);
		if (!(sel instanceof IStructuredSelection)) {
			return;
		}
		IMember member = (IMember) ((IStructuredSelection) sel).getFirstElement();
		int mtype = member.getElementType();
		if (mtype == IJavaElement.FIELD || mtype == IJavaElement.METHOD || mtype == IJavaElement.INITIALIZER) {
			toggleFieldOrMethodBreakpoints(part, selection);
		} else if (member.getElementType() == IJavaElement.TYPE) {
			if (BreakpointToggleUtils.isToggleTracepoints()) {
				BreakpointToggleUtils.report(ActionMessages.TracepointToggleAction_Unavailable, part);
				BreakpointToggleUtils.setUnsetTracepoints(false);
				return;
			}
			toggleClassBreakpoints(part, sel);
		} else {
			// fall back to old behavior, always create a line breakpoint
			toggleLineBreakpoints(part, selection, true, null);
		}
	}

	private static IJavaLineBreakpoint findExistingBreakpoint(ITextEditor editor, ITextSelection ts) {
		IDocumentProvider documentProvider = editor.getDocumentProvider();
		if (documentProvider == null) {
			return null;
		}
		IEditorInput editorInput = editor.getEditorInput();
		IAnnotationModel annotationModel = documentProvider.getAnnotationModel(editorInput);
		if (annotationModel == null) {
			return null;
		}
		IDocument document = documentProvider.getDocument(editorInput);
		if (document == null) {
			return null;
		}
		Iterator<Annotation> iterator = annotationModel.getAnnotationIterator();
		while (iterator.hasNext()) {
			Object object = iterator.next();
			if (!(object instanceof SimpleMarkerAnnotation)) {
				continue;
			}
			SimpleMarkerAnnotation markerAnnotation = (SimpleMarkerAnnotation) object;
			IMarker marker = markerAnnotation.getMarker();
			try {
				if (marker.isSubtypeOf(IBreakpoint.BREAKPOINT_MARKER)) {
					Position position = annotationModel.getPosition(markerAnnotation);
					int line = document.getLineOfOffset(position.getOffset());
					if (line == ts.getStartLine()) {
						IBreakpoint oldBreakpoint = DebugPlugin.getDefault().getBreakpointManager().getBreakpoint(marker);
						if (oldBreakpoint instanceof IJavaLineBreakpoint) {
							return (IJavaLineBreakpoint) oldBreakpoint;
						}
					}
				}
			} catch (BadLocationException e) {
				JDIDebugUIPlugin.log(e);
			} catch (CoreException e) {
				logBadAnnotation(markerAnnotation, e);
			}
		}
		return null;
	}

	private void toggleFieldOrMethodBreakpoints(IWorkbenchPart part, ISelection selection) throws CoreException {
		if (!(selection instanceof ITextSelection)) {
			return;
		}
		ITextSelection ts = (ITextSelection) selection;
		ITextEditor editor = getTextEditor(part);
		if (editor == null) {
			return;
		}
		// remove line breakpoint if present first
		IJavaLineBreakpoint breakpoint = findExistingBreakpoint(editor, ts);
		if (breakpoint != null) {
			if (BreakpointToggleUtils.isToggleTracepoints()) {
				deleteTracepoint(breakpoint, part, null);
				BreakpointToggleUtils.setUnsetTracepoints(false);
			} else {
				deleteBreakpoint(breakpoint, part, null);
			}
			return;
		}
		// no breakpoint found: we create new one
		CompilationUnit unit = parseCompilationUnit(editor);
		if (unit == null) {
			JDIDebugUIPlugin.log("Failed to parse CU for: " + editor.getTitle(), new IllegalStateException()); //$NON-NLS-1$
			return;
		}
		ValidBreakpointLocationLocator loc = null;
		boolean lambdaEntryBP = false;
		if (BreakpointToggleUtils.isToggleLambdaEntryBreakpoint()) {
			loc = new ValidBreakpointLocationLocator(unit, ts.getStartLine()
				+ 1, true, true, ts.getOffset(), ts.getLength());
			lambdaEntryBP = true;
			BreakpointToggleUtils.setUnsetLambdaEntryBreakpoint(false);
		} else {
			loc = new ValidBreakpointLocationLocator(unit, ts.getStartLine() + 1, true, true, ts.getOffset(), ts.getLength());
		}
		unit.accept(loc);

		if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LAMBDA_METHOD) {
			toggleLambdaMethodBreakpoints(part, ts, loc);
		} else if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_METHOD) {
			toggleMethodBreakpoints(part, ts);
		} else if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_FIELD) {
			if (BreakpointToggleUtils.isToggleTracepoints()) {
				BreakpointToggleUtils.report(ActionMessages.TracepointToggleAction_Unavailable, part);
				BreakpointToggleUtils.setUnsetTracepoints(false);
				return;
			}
			toggleWatchpoints(part, ts);
		} else if (loc.getLocationType() == ValidBreakpointLocationLocator.LOCATION_LINE) {
			if (lambdaEntryBP) {
				IEditorInput editorInput = editor.getEditorInput();
				IDocumentProvider documentProvider = editor.getDocumentProvider();
				if (documentProvider == null) {
					BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part);
					throw new CoreException(Status.CANCEL_STATUS);
				}
				IDocument document = documentProvider.getDocument(editorInput);
				try {
					IRegion region = document.getLineInformation(ts.getStartLine());
					FirstLambdaLocationLocator firstLambda = new FirstLambdaLocationLocator(region.getOffset(), region.getOffset() + region.getLength());
					unit.accept(firstLambda);
					if (firstLambda.getNodeLength() == -1 || firstLambda.getNodeOffset() == -1) {
						BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part);
						return;
					}
					ITextSelection textSelection = new TextSelection(document, firstLambda.getNodeOffset(), firstLambda.getNodeLength());
					toggleLambdaEntryMethodBreakpoints(part, textSelection, firstLambda.getLambdaMethodName(), firstLambda.getfLambdaMethodSignature());
				} catch (BadLocationException e) {
					BreakpointToggleUtils.report(ActionMessages.LambdaEntryBreakpointToggleAction_Unavailable, part);
				}

			} else {
				toggleLineBreakpoints(part, ts, false, loc);
			}
		}
	}

	/**
	 * Additional diagnosis info for bug 528321
	 */
	private static void logBadAnnotation(SimpleMarkerAnnotation annotation, CoreException e) {
		String message = "Editor annotation with non existing marker found: "; //$NON-NLS-1$
		message += "text: " + annotation.getText(); //$NON-NLS-1$
		message += ", type: " + annotation.getType(); //$NON-NLS-1$
		message += ", " + annotation.getMarker(); //$NON-NLS-1$
		JDIDebugUIPlugin.log(message, e);
	}

	/**
	 * Deletes the given breakpoint using the operation history, which allows to undo the deletion.
	 *
	 * @param breakpoint the breakpoint to delete
	 * @param part a workbench part, or <code>null</code> if unknown
	 * @param progressMonitor the progress monitor
	 * @throws CoreException if the deletion fails
	 */
	private static void deleteBreakpoint(IJavaBreakpoint breakpoint, IWorkbenchPart part, IProgressMonitor monitor) throws CoreException {
		final Shell shell = part != null ? part.getSite().getShell() : null;
		final boolean[] result = new boolean[] { true };

		final IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(JDIDebugUIPlugin.getUniqueIdentifier());
		boolean prompt = prefs.getBoolean(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT, true);
		if (prompt && breakpoint instanceof IJavaLineBreakpoint && ((IJavaLineBreakpoint) breakpoint).getCondition() != null) {
			Display display = shell != null && !shell.isDisposed() ? shell.getDisplay() : PlatformUI.getWorkbench().getDisplay();
			if (!display.isDisposed()) {
				display.syncExec(new Runnable() {
					@Override
					public void run() {
						MessageDialogWithToggle dialog = MessageDialogWithToggle.openOkCancelConfirm(shell, ActionMessages.ToggleBreakpointAdapter_confirmDeleteTitle, ActionMessages.ToggleBreakpointAdapter_confirmDeleteMessage, ActionMessages.ToggleBreakpointAdapter_confirmDeleteShowAgain, false, null, null);
						if (dialog.getToggleState()) {
							prefs.putBoolean(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT, false);
						}
						result[0] = dialog.getReturnCode() == IDialogConstants.OK_ID;
					}
				});
			}
		}
		if (result[0]) {
			DebugUITools.deleteBreakpoints(new IBreakpoint[] { breakpoint }, shell, monitor);
		}
	}

	private static void deleteTracepoint(IJavaBreakpoint breakpoint, IWorkbenchPart part, IProgressMonitor monitor) throws CoreException {
		final Shell shell = part != null ? part.getSite().getShell() : null;
		final IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(JDIDebugUIPlugin.getUniqueIdentifier());
		boolean prompt = prefs.getBoolean(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT, true);

		if (!prompt || !(breakpoint instanceof IJavaLineBreakpoint)) {
			DebugUITools.deleteBreakpoints(new IBreakpoint[] { breakpoint }, shell, monitor);
			return;
		}

		final boolean[] result = new boolean[] { true };
		String condition = ((IJavaLineBreakpoint) breakpoint).getCondition();
		boolean conditionChanged = true;
		if (condition != null) {
			int index = condition.indexOf(';');
			if (index != -1) {
				int lastIndex = condition.lastIndexOf(';');
				if (index == lastIndex) {
					conditionChanged = false;
				}

			} else {
				if (condition.indexOf("print") != -1) { //$NON-NLS-1$
					conditionChanged = false;
				}
			}
		}
		if (conditionChanged && condition != null) {
			Display display = shell != null && !shell.isDisposed() ? shell.getDisplay() : PlatformUI.getWorkbench().getDisplay();
			if (!display.isDisposed()) {
				display.syncExec(new Runnable() {
					@Override
					public void run() {
						MessageDialogWithToggle dialog = MessageDialogWithToggle.openOkCancelConfirm(shell, ActionMessages.ToggleBreakpointAdapter_confirmDeleteTitle, ActionMessages.ToggleBreakpointAdapter_confirmDeleteMessage, ActionMessages.ToggleBreakpointAdapter_confirmDeleteShowAgain, false, null, null);
						if (dialog.getToggleState()) {
							prefs.putBoolean(IJDIPreferencesConstants.PREF_PROMPT_DELETE_CONDITIONAL_BREAKPOINT, false);
						}
						result[0] = dialog.getReturnCode() == IDialogConstants.OK_ID;
					}
				});
			}
		}

		if (result[0]) {
			DebugUITools.deleteBreakpoints(new IBreakpoint[] { breakpoint }, shell, monitor);
		}
	}

    @Override
	public boolean canToggleBreakpoints(IWorkbenchPart part, ISelection selection) {
    	if (isRemote(part, selection)) {
    		return false;
    	}
        return canToggleLineBreakpoints(part, selection);
    }

	@Override
	public void toggleBreakpointsWithEvent(IWorkbenchPart part, ISelection selection, Event event) throws CoreException {
		if (event == null) {
			toggleBreakpoints(part, selection);
			return;
		}
		if ((event.stateMask & SWT.MOD2) > 0) {
			ITextEditor editor = getTextEditor(part);
			if (editor != null) {
				IVerticalRulerInfo info = editor.getAdapter(IVerticalRulerInfo.class);
				if (info != null) {
					IBreakpoint bp = BreakpointUtils.getBreakpointFromEditor(editor, info);
					if (bp != null) {
						bp.setEnabled(!bp.isEnabled());
						return;
					}
				}
			}
		} else if ((event.stateMask & SWT.MOD1) > 0) {
			ITextEditor editor = getTextEditor(part);
			if (editor != null) {
				IVerticalRulerInfo info = editor.getAdapter(IVerticalRulerInfo.class);
				if (info != null) {
					IBreakpoint bp = BreakpointUtils.getBreakpointFromEditor(editor, info);
					if (bp != null) {
						PreferencesUtil.createPropertyDialogOn(editor.getSite().getShell(), bp, null, null, null).open();
						return;
					}
				}
			}
		}
		toggleBreakpoints(part, selection);
	}

	@Override
	public boolean canToggleBreakpointsWithEvent(IWorkbenchPart part, ISelection selection, Event event) {
		return canToggleBreakpoints(part, selection);
	}

	/**
	 * Returns the {@link ITypeRoot} for the given {@link IEditorInput}
	 *
	 * @param input
	 * @return the type root or <code>null</code> if one cannot be derived
	 * @since 3.8
	 */
	@SuppressWarnings("deprecation")
	private static String getCodeTemplate(ITextSelection textSelection, JavaEditor part) {
		ITextViewer viewer = part.getViewer();
		if (viewer == null) {
			return null;
		}
		TemplateContextType contextType = JavaPlugin.getDefault().getTemplateContextRegistry().getContextType(JavaContextType.ID_STATEMENTS);
		final AtomicReference<String> templateBuffer = new AtomicReference<>();
		Display.getDefault().syncExec(() -> doGetCodeTemplate(textSelection, part, viewer, contextType, templateBuffer));
		return templateBuffer.get();
	}

	@SuppressWarnings("deprecation")
	private static void doGetCodeTemplate(ITextSelection textSelection, JavaEditor part, ITextViewer viewer, TemplateContextType contextType, AtomicReference<String> templateBuffer) {
		ITextEditor editor = getTextEditor(part);
		if (editor == null) {
			return;
		}
		IDocumentProvider documentProvider = editor.getDocumentProvider();
		if (documentProvider == null) {
			return;
		}
		ICompilationUnit cunit = getCompilationUnit(editor);
		if (cunit == null) {
			return;
		}
		TemplateEngine statementEngine = new TemplateEngine(contextType);
		statementEngine.reset();
		IDocument document = documentProvider.getDocument(editor.getEditorInput());
		try {
			IRegion line = document.getLineInformation(textSelection.getStartLine() + 1);
			Point selectedRange = new Point(textSelection.getOffset(), textSelection.getLength());
			statementEngine.complete(viewer, selectedRange, line.getOffset(), cunit);
			TemplateProposal[] templateProposals = statementEngine.getResults();
			for (TemplateProposal templateProposal : templateProposals) {
				Template template = templateProposal.getTemplate();
				if (template.getName().equals("systrace")) { //$NON-NLS-1$
					CompilationUnitContextType cuContextType = (CompilationUnitContextType) JavaPlugin.getDefault().getTemplateContextRegistry().getContextType(template.getContextTypeId());
					DocumentTemplateContext context = cuContextType.createContext(document, line.getOffset(), 0, cunit);
					context.setVariable("selection", EMPTY_STRING); //$NON-NLS-1$
					((CompilationUnitContext) context).setForceEvaluation(true);
					templateBuffer.set(context.evaluate(template).getString());
					return;
				}
			}
		} catch (BadLocationException | TemplateException e) {
			// ignore
		}
	}

	private static ICompilationUnit getCompilationUnit(ITextEditor editor) {
		IJavaElement element = getJavaElement(editor.getEditorInput());
		if (element == null) {
			return null;
		}
		if (element instanceof IOrdinaryClassFile) {
			try {
				return ((IOrdinaryClassFile) element).getWorkingCopy(new WorkingCopyOwner() {}, null);
			} catch (JavaModelException e) {
				// ignore
				return null;
			}
		}
		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
		IFile file = root.getFile(element.getPath());
		return JavaCore.createCompilationUnitFrom(file);
	}

}
