/*******************************************************************************
 * Copyright (c) 2000, 2015 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.model;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.DebugElement;
import org.eclipse.debug.core.model.IDebugElement;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IDisconnect;
import org.eclipse.debug.core.model.IStepFilters;
import org.eclipse.debug.core.model.ITerminate;
import org.eclipse.jdi.TimeoutException;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.EventDispatcher;
import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;

import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;

public abstract class JDIDebugElement extends DebugElement implements
		IDisconnect {

	/**
	 * Creates a JDI debug element associated with the specified debug target.
	 *
	 * @param target
	 *            The associated debug target
	 */
	public JDIDebugElement(JDIDebugTarget target) {
		super(target);
	}

	/**
	 * Convenience method to log errors
	 */
	protected void logError(Exception e) {
		if (!((JDIDebugTarget) getDebugTarget()).isAvailable()) {
			// Don't log VMDisconnectedExceptions that occur
			// when the VM is unavailable.
			if (e instanceof VMDisconnectedException
					|| (e instanceof CoreException && ((CoreException) e)
							.getStatus().getException() instanceof VMDisconnectedException)) {
				return;
			}
		}
		JDIDebugPlugin.log(e);
	}

	/**
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (adapter == IDebugElement.class) {
			return (T) this;
		}
		if (adapter == IStepFilters.class) {
			return (T) getDebugTarget();
		}
		if (adapter == IDebugTarget.class) {
			return (T) getDebugTarget();
		}
		if (adapter == ITerminate.class) {
			return (T) getDebugTarget();
		}
		if (adapter == IJavaDebugTarget.class) {
			return (T) getJavaDebugTarget();
		}
		return super.getAdapter(adapter);
	}

	/**
	 * @see org.eclipse.debug.core.model.IDebugElement#getModelIdentifier()
	 */
	@Override
	public String getModelIdentifier() {
		return JDIDebugModel.getPluginIdentifier();
	}

	/**
	 * Queues a debug event with the event dispatcher to be fired as an event
	 * set when all event processing is complete.
	 *
	 * @param event
	 *            the event to queue
	 * @param set
	 *            the event set the event is associated with
	 */
	public void queueEvent(DebugEvent event, EventSet set) {
		EventDispatcher dispatcher = ((JDIDebugTarget) getDebugTarget())
				.getEventDispatcher();
		if (dispatcher != null) {
			dispatcher.queue(event, set);
		}
	}

	/**
	 * Fires a debug event marking the SUSPEND of this element with the
	 * associated detail.
	 *
	 * @param detail
	 *            The int detail of the event
	 * @see org.eclipse.debug.core.DebugEvent
	 */
	@Override
	public void fireSuspendEvent(int detail) {
		getJavaDebugTarget().incrementSuspendCount(detail);
		super.fireSuspendEvent(detail);
	}

	/**
	 * Queues a debug event marking the SUSPEND of this element with the
	 * associated detail.
	 *
	 * @param detail
	 *            The int detail of the event
	 * @param set
	 *            the event set the event is associated with
	 * @see org.eclipse.debug.core.DebugEvent
	 */
	public void queueSuspendEvent(int detail, EventSet set) {
		getJavaDebugTarget().incrementSuspendCount(detail);
		queueEvent(new DebugEvent(this, DebugEvent.SUSPEND, detail), set);
	}

	/**
	 * Throws a new debug exception with a status code of
	 * <code>REQUEST_FAILED</code>.
	 *
	 * @param message
	 *            Failure message
	 * @param e
	 *            Exception that has occurred (<code>can be null</code>)
	 * @throws DebugException
	 *             The exception with a status code of
	 *             <code>REQUEST_FAILED</code>
	 */
	public void requestFailed(String message, Exception e)
			throws DebugException {
		requestFailed(message, e, DebugException.REQUEST_FAILED);
	}

	/**
	 * Throws a new debug exception with a status code of
	 * <code>TARGET_REQUEST_FAILED</code> with the given underlying exception.
	 * If the underlying exception is not a JDI exception, the original
	 * exception is thrown.
	 *
	 * @param message
	 *            Failure message
	 * @param e
	 *            underlying exception that has occurred
	 * @throws DebugException
	 *             The exception with a status code of
	 *             <code>TARGET_REQUEST_FAILED</code>
	 */
	public void targetRequestFailed(String message, RuntimeException e)
			throws DebugException {
		if (e == null
				|| e.getClass().getName().startsWith("com.sun.jdi") || e instanceof TimeoutException) { //$NON-NLS-1$
			requestFailed(message, e, DebugException.TARGET_REQUEST_FAILED);
		} else {
			throw e;
		}
	}

	/**
	 * Throws a new debug exception with the given status code.
	 *
	 * @param message
	 *            Failure message
	 * @param e
	 *            Exception that has occurred (<code>can be null</code>)
	 * @param code
	 *            status code
	 * @throws DebugException
	 *             a new exception with given status code
	 */
	public void requestFailed(String message, Throwable e, int code)
			throws DebugException {
		throwDebugException(message, code, e);
	}

	/**
	 * Throws a new debug exception with a status code of
	 * <code>TARGET_REQUEST_FAILED</code>.
	 *
	 * @param message
	 *            Failure message
	 * @param e
	 *            Throwable that has occurred
	 * @throws DebugException
	 *             The exception with a status code of
	 *             <code>TARGET_REQUEST_FAILED</code>
	 */
	public void targetRequestFailed(String message, Throwable e)
			throws DebugException {
		throwDebugException(message, DebugException.TARGET_REQUEST_FAILED, e);
	}

	/**
	 * Throws a new debug exception with a status code of
	 * <code>TARGET_REQUEST_FAILED</code> with the given underlying exception.
	 * The underlying exception is an exception thrown by a JDI request.
	 *
	 * @param message
	 *            Failure message
	 * @param e
	 *            throwable exception that has occurred
	 * @throws DebugException
	 *             the exception with a status code of
	 *             <code>TARGET_REQUEST_FAILED</code>
	 */
	public void jdiRequestFailed(String message, Throwable e)
			throws DebugException {
		throwDebugException(message, DebugException.TARGET_REQUEST_FAILED, e);
	}

	/**
	 * Throws a new debug exception with a status code of
	 * <code>NOT_SUPPORTED</code>.
	 *
	 * @param message
	 *            Failure message
	 * @throws DebugException
	 *             The exception with a status code of
	 *             <code>NOT_SUPPORTED</code>.
	 */
	public void notSupported(String message) throws DebugException {
		throwDebugException(message, DebugException.NOT_SUPPORTED, null);
	}

	/**
	 * Throws a debug exception with the given message, error code, and
	 * underlying exception.
	 */
	protected void throwDebugException(String message, int code,
			Throwable exception) throws DebugException {
		if (exception instanceof VMDisconnectedException) {
			disconnected();
		}
		throw new DebugException(new Status(IStatus.ERROR,
				JDIDebugModel.getPluginIdentifier(), code, message, exception));
	}

	/**
	 * Logs the given exception if it is a JDI exception, otherwise throws the
	 * runtime exception.
	 *
	 * @param e
	 *            The internal runtime exception
	 */
	public void internalError(RuntimeException e) {
		if (e.getClass().getName().startsWith("com.sun.jdi") || e instanceof TimeoutException) { //$NON-NLS-1$
			logError(e);
		} else {
			throw e;
		}
	}

	/**
	 * Logs a debug exception with the given message, with a status code of
	 * <code>INTERNAL_ERROR</code>.
	 *
	 * @param message
	 *            The internal error message
	 */
	protected void internalError(String message) {
		logError(new DebugException(new Status(IStatus.ERROR,
				JDIDebugModel.getPluginIdentifier(),
				DebugException.INTERNAL_ERROR, message, null)));
	}

	/**
	 * Returns the common "&lt;unknown&gt;" message.
	 *
	 * @return the unknown String
	 */
	protected String getUnknownMessage() {
		return JDIDebugModelMessages.JDIDebugElement_unknown;
	}

	/**
	 * Returns this elements debug target as its implementation class.
	 *
	 * @return Java debug target
	 */
	public JDIDebugTarget getJavaDebugTarget() {
		return (JDIDebugTarget) getDebugTarget();
	}

	/**
	 * Returns the target VM associated with this element, or <code>null</code>
	 * if none.
	 *
	 * @return target VM or <code>null</code> if none
	 */
	protected VirtualMachine getVM() {
		return ((JDIDebugTarget) getDebugTarget()).getVM();
	}

	/**
	 * Returns the underlying VM's event request manager, or <code>null</code>
	 * if none (disconnected/terminated)
	 *
	 * @return event request manager or <code>null</code>
	 */
	public EventRequestManager getEventRequestManager() {
		VirtualMachine vm = getVM();
		if (vm == null) {
			return null;
		}
		return vm.eventRequestManager();
	}

	/**
	 * Adds the given listener to this target's event dispatcher's table of
	 * listeners for the specified event request. The listener will be notified
	 * each time the event occurs.
	 *
	 * @param listener
	 *            the listener to register
	 * @param request
	 *            the event request
	 */
	public void addJDIEventListener(IJDIEventListener listener,
			EventRequest request) {
		EventDispatcher dispatcher = ((JDIDebugTarget) getDebugTarget())
				.getEventDispatcher();
		if (dispatcher != null) {
			dispatcher.addJDIEventListener(listener, request);
		}
	}

	/**
	 * Removes the given listener from this target's event dispatcher's table of
	 * listeners for the specifed event request. The listener will no longer be
	 * notified when the event occurs. Listeners are responsible for deleting
	 * the event request if desired.
	 *
	 * @param listener
	 *            the listener to remove
	 * @param request
	 *            the event request
	 */
	public void removeJDIEventListener(IJDIEventListener listener,
			EventRequest request) {
		EventDispatcher dispatcher = ((JDIDebugTarget) getDebugTarget())
				.getEventDispatcher();
		if (dispatcher != null) {
			dispatcher.removeJDIEventListener(listener, request);
		}
	}

	/**
	 * The VM has disconnected. Notify the target.
	 */
	protected void disconnected() {
		if (getDebugTarget() != null) {
			getJavaDebugTarget().disconnected();
		}
	}

	/**
	 * @see IJavaDebugTarget#setRequestTimeout(int)
	 */
	public void setRequestTimeout(int timeout) {
		if (supportsRequestTimeout()) {
			VirtualMachine vm = getVM();
			if (vm != null) {
				((org.eclipse.jdi.VirtualMachine) vm)
						.setRequestTimeout(timeout);
			}
		}
	}

	/**
	 * @see IJavaDebugTarget#getRequestTimeout()
	 */
	public int getRequestTimeout() {
		if (supportsRequestTimeout()) {
			VirtualMachine vm = getVM();
			if (vm != null) {
				return ((org.eclipse.jdi.VirtualMachine) vm)
						.getRequestTimeout();
			}
		}
		return -1;
	}

	/**
	 * @see IJavaDebugTarget#supportsRequestTimeout()
	 */
	public boolean supportsRequestTimeout() {
		return getJavaDebugTarget().isAvailable()
				&& getVM() instanceof org.eclipse.jdi.VirtualMachine;
	}

	/**
	 * @see org.eclipse.debug.core.model.IDisconnect#canDisconnect()
	 */
	@Override
	public boolean canDisconnect() {
		return getDebugTarget().canDisconnect();
	}

	/**
	 * @see org.eclipse.debug.core.model.IDisconnect#disconnect()
	 */
	@Override
	public void disconnect() throws DebugException {
		getDebugTarget().disconnect();
	}

	/**
	 * @see org.eclipse.debug.core.model.IDisconnect#isDisconnected()
	 */
	@Override
	public boolean isDisconnected() {
		return getDebugTarget().isDisconnected();
	}

	/**
	 * @see org.eclipse.debug.core.model.IStepFilters#isStepFiltersEnabled()
	 */
	public boolean isStepFiltersEnabled() {
		return getJavaDebugTarget().isStepFiltersEnabled();
	}
}
