/*******************************************************************************
 * Copyright (c) 2000, 2011 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.core.breakpoints;

import java.util.Map;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.jdt.debug.core.IJavaClassPrepareBreakpoint;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;

import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;

/**
 * Class prepare breakpoint.
 *
 * @since 3.0
 */
public class JavaClassPrepareBreakpoint extends JavaBreakpoint implements
		IJavaClassPrepareBreakpoint {

	public static final String JAVA_CLASS_PREPARE_BREAKPOINT = "org.eclipse.jdt.debug.javaClassPrepareBreakpointMarker"; //$NON-NLS-1$

	/**
	 * Class prepare breakpoint attribute storing the type of member this
	 * breakpoint is associated with (value
	 * <code>"org.eclipse.jdt.debug.core.memberType"</code>), encoded as an
	 * integer.
	 */
	protected static final String MEMBER_TYPE = "org.eclipse.jdt.debug.core.memberType"; //$NON-NLS-1$

	/**
	 * Creates and returns a Java class prepare breakpoint for the given type.
	 *
	 * @param resource
	 *            the resource on which to create the associated breakpoint
	 *            marker
	 * @param typeName
	 *            the fully qualified name of the type for which to create the
	 *            breakpoint
	 * @param memberType
	 *            one of <code>TYPE_CLASS</code> or <code>TYPE_INTERFACE</code>
	 * @param charStart
	 *            the first character index associated with the breakpoint, or
	 *            -1 if unspecified, in the source file in which the breakpoint
	 *            is set
	 * @param charEnd
	 *            the last character index associated with the breakpoint, or -1
	 *            if unspecified, in the source file in which the breakpoint is
	 *            set
	 * @param add
	 *            whether to add this breakpoint to the breakpoint manager
	 * @return a Java class prepare breakpoint
	 * @exception DebugException
	 *                if unable to create the associated marker due to a lower
	 *                level exception.
	 */
	public JavaClassPrepareBreakpoint(final IResource resource,
			final String typeName, final int memberType, final int charStart,
			final int charEnd, final boolean add, final Map<String, Object> attributes)
			throws DebugException {
		IWorkspaceRunnable wr = new IWorkspaceRunnable() {

			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				// create the marker
				setMarker(resource.createMarker(JAVA_CLASS_PREPARE_BREAKPOINT));

				// add attributes
				attributes.put(IBreakpoint.ID, getModelIdentifier());
				attributes.put(IMarker.CHAR_START, new Integer(charStart));
				attributes.put(IMarker.CHAR_END, new Integer(charEnd));
				attributes.put(TYPE_NAME, typeName);
				attributes.put(MEMBER_TYPE, new Integer(memberType));
				attributes.put(ENABLED, Boolean.TRUE);
				attributes.put(SUSPEND_POLICY, new Integer(
						getDefaultSuspendPolicy()));

				ensureMarker().setAttributes(attributes);

				register(add);
			}

		};
		run(getMarkerRule(resource), wr);
	}

	public JavaClassPrepareBreakpoint() {
	}

	/**
	 * Creates event requests for the given target
	 */
	@Override
	protected void createRequests(JDIDebugTarget target) throws CoreException {
		if (target.isTerminated() || shouldSkipBreakpoint()) {
			return;
		}
		String referenceTypeName = getTypeName();
		if (referenceTypeName == null) {
			return;
		}

		ClassPrepareRequest request = target.createClassPrepareRequest(
				referenceTypeName, null, false);
		configureRequestHitCount(request);
		updateEnabledState(request, target);
		registerRequest(request, target);
		// TODO: do we show anything for types already loaded?
		incrementInstallCount();
	}

	/**
	 * Remove the given request from the given target. If the request is the
	 * breakpoint request associated with this breakpoint, decrement the install
	 * count.
	 */
	@Override
	protected void deregisterRequest(EventRequest request, JDIDebugTarget target)
			throws CoreException {
		target.removeJDIEventListener(this, request);
		// A request may be getting deregistered because the breakpoint has
		// been deleted. It may be that this occurred because of a marker
		// deletion.
		// Don't try updating the marker (decrementing the install count) if
		// it no longer exists.
		if (getMarker().exists()) {
			decrementInstallCount();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * Not supported for class prepare breakpoints.
	 *
	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
	 * addInstanceFilter(com.sun.jdi.request.EventRequest,
	 * com.sun.jdi.ObjectReference)
	 */
	@Override
	protected void addInstanceFilter(EventRequest request,
			ObjectReference object) {
	}

	/*
	 * (non-Javadoc)
	 *
	 * This method not used for class prepare breakpoints.
	 *
	 * @see
	 * org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#newRequest
	 * (org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget,
	 * com.sun.jdi.ReferenceType)
	 */
	@Override
	protected EventRequest[] newRequests(JDIDebugTarget target,
			ReferenceType type) throws CoreException {
		return null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * Not supported for class prepare breakpoints.
	 *
	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
	 * setRequestThreadFilter(com.sun.jdi.request.EventRequest,
	 * com.sun.jdi.ThreadReference)
	 */
	@Override
	protected void setRequestThreadFilter(EventRequest request,
			ThreadReference thread) {
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
	 * handleClassPrepareEvent(com.sun.jdi.event.ClassPrepareEvent,
	 * org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
	 */
	@Override
	public boolean handleClassPrepareEvent(ClassPrepareEvent event,
			JDIDebugTarget target, boolean suspendVote) {
		try {
			if (isEnabled()
					&& event.referenceType().name().equals(getTypeName())) {
				ThreadReference threadRef = event.thread();
				JDIThread thread = target.findThread(threadRef);
				if (thread == null || thread.isIgnoringBreakpoints()) {
					return true;
				}
				return handleBreakpointEvent(event, thread, suspendVote);
			}
		} catch (CoreException e) {
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint#
	 * classPrepareComplete(com.sun.jdi.event.Event,
	 * org.eclipse.jdt.internal.debug.core.model.JDIThread, boolean)
	 */
	@Override
	protected void classPrepareComplete(Event event, JDIThread thread,
			boolean suspend, EventSet eventSet) {
		thread.completeBreakpointHandling(this, suspend, true, eventSet);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.debug.core.IJavaClassPrepareBreakpoint#getMemberType()
	 */
	@Override
	public int getMemberType() throws CoreException {
		return ensureMarker().getAttribute(MEMBER_TYPE,
				IJavaClassPrepareBreakpoint.TYPE_CLASS);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#supportsInstanceFilters()
	 */
	@Override
	public boolean supportsInstanceFilters() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.debug.core.IJavaBreakpoint#addInstanceFilter(org.eclipse
	 * .jdt.debug.core.IJavaObject)
	 */
	@Override
	public void addInstanceFilter(IJavaObject object) throws CoreException {
		throw new CoreException(new Status(IStatus.ERROR,
				JDIDebugModel.getPluginIdentifier(),
				DebugException.REQUEST_FAILED,
				JDIDebugBreakpointMessages.JavaClassPrepareBreakpoint_2, null));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.eclipse.jdt.debug.core.IJavaBreakpoint#setThreadFilter(org.eclipse
	 * .jdt.debug.core.IJavaThread)
	 */
	@Override
	public void setThreadFilter(IJavaThread thread) throws CoreException {
		throw new CoreException(new Status(IStatus.ERROR,
				JDIDebugModel.getPluginIdentifier(),
				DebugException.REQUEST_FAILED,
				JDIDebugBreakpointMessages.JavaClassPrepareBreakpoint_3, null));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.eclipse.jdt.debug.core.IJavaBreakpoint#supportsThreadFilters()
	 */
	@Override
	public boolean supportsThreadFilters() {
		return false;
	}
}
