/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.eclipse.debug.core;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.drools.eclipse.DroolsEclipsePlugin;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.jdi.TimeoutException;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.internal.debug.core.IJDIEventListener;
import org.eclipse.jdt.internal.debug.core.breakpoints.JavaBreakpoint;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugModelMessages;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIObjectValue;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;

import com.sun.jdi.ClassType;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.Location;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.event.BreakpointEvent;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ThreadDeathEvent;
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.BreakpointRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;

public class DroolsDebugTarget extends JDIDebugTarget {

    private ArrayList<JDIThread>          fThreads;
    private ThreadStartHandler fThreadStartHandler = null;
    private boolean            fSuspended          = true;

    public DroolsDebugTarget(ILaunch launch,
                             VirtualMachine jvm,
                             String name,
                             boolean supportTerminate,
                             boolean supportDisconnect,
                             IProcess process,
                             boolean resume) {
        super( launch,
               jvm,
               name,
               supportTerminate,
               supportDisconnect,
               process,
               resume );
    }

    public void breakpointAdded(IBreakpoint breakpoint) {

        try {
            if ( breakpoint instanceof DroolsLineBreakpoint ) {
                ((DroolsLineBreakpoint) breakpoint).setJavaBreakpointProperties();

                final DroolsLineBreakpoint d = (DroolsLineBreakpoint) breakpoint;

                if ( d.getDialectName().equals( "mvel" ) ) {
                    //getBreakpoints().add( breakpoint );
                    //super.breakpointAdded(breakpoint);

                    Iterator<ReferenceType> handleriter = getVM().classesByName( "org.drools.core.base.mvel.MVELDebugHandler" ).iterator();
                    if ( !handleriter.hasNext() ) {
                        // Create class prepare request to add breakpoint after MVELDebugHanlder is loaded
                        ClassPrepareRequest req = getEventRequestManager().createClassPrepareRequest();
                        req.addClassFilter( "org.drools.core.base.mvel.MVELDebugHandler" );
                        req.setSuspendPolicy( EventRequest.SUSPEND_ALL );

                        addJDIEventListener(new IJDIEventListener() {
                            public boolean handleEvent(Event event,    JDIDebugTarget target) {
                                addRemoteBreakpoint(d);
                                return true;
                            }

                            public void wonSuspendVote(Event event, JDIDebugTarget target) {
                            }

                            public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
                                wonSuspendVote(event, target);
                            }

                            public boolean handleEvent(Event event,    JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
                                return handleEvent(event, target);
                            }

                        }, req);

                        req.enable();
                        return;
                    }

                    addRemoteBreakpoint( d );
                } else {
                    // only add breakpoint if setting Java properties of DRL
                    // breakpoint does not generate an error
                    super.breakpointAdded( breakpoint );
                }
            } else {
                super.breakpointAdded( breakpoint );
            }
        } catch ( Throwable t ) {
            // Exception will be thrown when trying to use breakpoint
            // on drl that is incorrect (cannot be parsed or compiled)
            DroolsEclipsePlugin.log( t );
        }
    }

    protected synchronized void initialize() {
        setThreadList( new ArrayList<JDIThread>( 5 ) );
        super.initialize();
    }

    protected JDIThread createThread(ThreadReference thread) {
        JDIThread jdiThread = null;
        try {
            jdiThread = new DroolsThread( this,
                                          thread );
        } catch ( ObjectCollectedException exception ) {
            // ObjectCollectionException can be thrown if the thread has already
            // completed (exited) in the VM.
            return null;
        }
        if ( isDisconnected() ) {
            return null;
        }
        synchronized ( fThreads ) {
            fThreads.add( jdiThread );
        }
        jdiThread.fireCreationEvent();
        return jdiThread;
    }

    @SuppressWarnings("unchecked")
    private Iterator<JDIThread> getThreadIterator() {
        List<JDIThread> threadList;
        synchronized ( fThreads ) {
            threadList = (List<JDIThread>) fThreads.clone();
        }
        return threadList.iterator();
    }

    private boolean hasSuspendedThreads() {
        Iterator<JDIThread> it = getThreadIterator();
        while ( it.hasNext() ) {
            IThread thread = it.next();
            if ( thread.isSuspended() ) return true;
        }
        return false;
    }

    public boolean canResume() {
        return (isSuspended() || hasSuspendedThreads()) && isAvailable() && !isPerformingHotCodeReplace();
    }

    protected void resume(boolean fireNotification) throws DebugException {
        if ( (!isSuspended() && !hasSuspendedThreads()) || !isAvailable() ) {
            return;
        }
        try {
            setSuspended( false );
            resumeThreads();
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.resume();
            }
            if ( fireNotification ) {
                fireResumeEvent( DebugEvent.CLIENT_REQUEST );
            }
        } catch ( VMDisconnectedException e ) {
            disconnected();
            return;
        } catch ( RuntimeException e ) {
            setSuspended( true );
            fireSuspendEvent( DebugEvent.CLIENT_REQUEST );
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_resume, e.toString()), e);
        }
    }

    private void setSuspended(boolean suspended) {
        fSuspended = suspended;
    }

    public boolean isSuspended() {
        return fSuspended;
    }

    private void setThreadList(ArrayList<JDIThread> threads) {
        fThreads = threads;
    }

    public IThread[] getThreads() {
        synchronized ( fThreads ) {
            return fThreads.toArray( new IThread[0] );
        }
    }

    protected void removeAllThreads() {
        Iterator<JDIThread> itr = getThreadIterator();
        while ( itr.hasNext() ) {
            DroolsThread child = (DroolsThread) itr.next();
            child.terminated();
        }
        synchronized ( fThreads ) {
            fThreads.clear();
        }
    }

    protected void initializeRequests() {
        setThreadStartHandler( new ThreadStartHandler() );
        new ThreadDeathHandler();
        new MVELTraceHandler();
    }

    class ThreadDeathHandler
        implements
        IJDIEventListener {

        protected ThreadDeathHandler() {
            createRequest();
        }

        /**
         * Creates and registers a request to listen to thread
         * death events.
         */
        protected void createRequest() {
            EventRequestManager manager = getEventRequestManager();
            if ( manager != null ) {
                try {
                    EventRequest req = manager.createThreadDeathRequest();
                    req.setSuspendPolicy( EventRequest.SUSPEND_NONE );
                    req.enable();
                    addJDIEventListener( this,
                                         req );
                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }

        /**
         * Locates the model thread associated with the underlying JDI thread
         * that has terminated, and removes it from the collection of
         * threads belonging to this debug target. A terminate event is
         * fired for the model thread.
         *
         * @param event a thread death event
         * @param target the target in which the thread died
         * @return <code>true</code> - the thread should be resumed
         */
        public boolean handleEvent(Event event,
                                   JDIDebugTarget target) {
            ThreadReference ref = ((ThreadDeathEvent) event).thread();
            DroolsThread thread = (DroolsThread) findThread( ref );
            if ( thread != null ) {
                synchronized ( fThreads ) {
                    fThreads.remove( thread );
                }
                thread.terminated();
            }
            return true;
        }

        public void wonSuspendVote(Event event, JDIDebugTarget target) {
            // do nothing
        }

        public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
            wonSuspendVote(event, target);
        }

        public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
            return handleEvent(event, target);
        }

    }

    class ThreadStartHandler
        implements
        IJDIEventListener {

        protected EventRequest fRequest;

        protected ThreadStartHandler() {
            createRequest();
        }

        /**
         * Creates and registers a request to handle all thread start
         * events
         */
        protected void createRequest() {
            EventRequestManager manager = getEventRequestManager();
            if ( manager != null ) {
                try {
                    EventRequest req = manager.createThreadStartRequest();
                    req.setSuspendPolicy( EventRequest.SUSPEND_NONE );
                    req.enable();
                    addJDIEventListener( this,
                                         req );
                    setRequest( req );
                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }

        /**
         * Creates a model thread for the underlying JDI thread
         * and adds it to the collection of threads for this
         * debug target. As a side effect of creating the thread,
         * a create event is fired for the model thread.
         * The event is ignored if the underlying thread is already
         * marked as collected.
         *
         * @param event a thread start event
         * @param target the target in which the thread started
         * @return <code>true</code> - the thread should be resumed
         */
        public boolean handleEvent(Event event,
                                   JDIDebugTarget target) {
            ThreadReference thread = ((ThreadStartEvent) event).thread();
            try {
                if ( thread.isCollected() ) {
                    return false;
                }
            } catch ( VMDisconnectedException exception ) {
                return false;
            } catch ( ObjectCollectedException e ) {
                return false;
            } catch ( TimeoutException e ) {
                // continue - attempt to create the thread
            }
            DroolsThread jdiThread = (DroolsThread) findThread( thread );
            if ( jdiThread == null ) {
                jdiThread = (DroolsThread) createThread( thread );
                if ( jdiThread == null ) {
                    return false;
                }
            } else {
                jdiThread.disposeStackFrames();
                jdiThread.fireChangeEvent( DebugEvent.CONTENT );
            }
            return !jdiThread.isSuspended();
        }

        /* (non-Javadoc)
         * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
         */
        public void wonSuspendVote(Event event,
                                   JDIDebugTarget target) {
            // do nothing
        }

        /**
         * Deregisters this event listener.
         */
        protected void deleteRequest() {
            if ( getRequest() != null ) {
                removeJDIEventListener( this,
                                        getRequest() );
                setRequest( null );
            }
        }

        protected EventRequest getRequest() {
            return fRequest;
        }

        protected void setRequest(EventRequest request) {
            fRequest = request;
        }

        public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
            wonSuspendVote(event, target);

        }

        public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
            return handleEvent(event, target);
        }
    }

    private void disposeThreadHandler() {
        ThreadStartHandler handler = getThreadStartHandler2();
        if ( handler != null ) {
            handler.deleteRequest();
        }
    }

    public boolean hasThreads() {
        return fThreads.size() > 0;
    }

    protected ThreadStartHandler getThreadStartHandler2() {
        return fThreadStartHandler;
    }

    protected void setThreadStartHandler(ThreadStartHandler threadStartHandler) {
        fThreadStartHandler = threadStartHandler;
    }

    public boolean isOutOfSynch() throws DebugException {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            JDIThread thread = threads.next();
            if ( thread.isOutOfSynch() ) {
                return true;
            }
        }
        return false;
    }

    public boolean mayBeOutOfSynch() {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            JDIThread thread = threads.next();
            if ( thread.mayBeOutOfSynch() ) {
                return true;
            }
        }
        return false;
    }

    public JDIThread findThread(ThreadReference tr) {
        Iterator<JDIThread> iter = getThreadIterator();
        while ( iter.hasNext() ) {
            JDIThread thread = iter.next();
            if ( thread.getUnderlyingThread().equals( tr ) ) return thread;
        }
        return null;
    }

    public void breakpointRemoved(IBreakpoint breakpoint,
                                  IMarkerDelta delta) {
        if ( !isAvailable() ) {
            return;
        }
        if ( supportsBreakpoint( breakpoint ) ) {
            try {

                if ( breakpoint instanceof DroolsLineBreakpoint ) {
                    ((DroolsLineBreakpoint) breakpoint).setJavaBreakpointProperties();

                    final DroolsLineBreakpoint d = (DroolsLineBreakpoint) breakpoint;

                    if ( d.getDialectName().equals( "mvel" ) ) {
                        removeRemoteBreakpoint( (DroolsLineBreakpoint) breakpoint,
                                                delta );
                    }
                }

                ((JavaBreakpoint) breakpoint).removeFromTarget( this );
                getBreakpoints().remove( breakpoint );
                Iterator<JDIThread> threads = getThreadIterator();
                while ( threads.hasNext() ) {
                    ((DroolsThread) threads.next()).removeCurrentBreakpoint( breakpoint );
                }
            } catch ( CoreException e ) {
                logError( e );
            }
        }
    }

    protected void suspendThreads() {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            ((DroolsThread) threads.next()).suspendedByVM();
        }
    }

    protected void resumeThreads() throws DebugException {
        Iterator<JDIThread> threads = getThreadIterator();
        while ( threads.hasNext() ) {
            ((DroolsThread) threads.next()).resumedByVM();
        }
    }

    public void disconnect() throws DebugException {

        if ( !isAvailable() ) {
            // already done
            return;
        }

        if ( !canDisconnect() ) {
            notSupported( JDIDebugModelMessages.JDIDebugTarget_does_not_support_disconnect );
        }

        try {
            disposeThreadHandler();
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.dispose();
            }
        } catch ( VMDisconnectedException e ) {
            // if the VM disconnects while disconnecting, perform
            // normal disconnect handling
            disconnected();
        } catch ( RuntimeException e ) {
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_disconnecting, e.toString()), e);
        }

    }

    public void terminate() throws DebugException {
        if ( !isAvailable() ) {
            return;
        }
        if ( !supportsTerminate() ) {
            notSupported( JDIDebugModelMessages.JDIDebugTarget_does_not_support_termination );
        }
        try {
            setTerminating( true );
            disposeThreadHandler();
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.exit( 1 );
            }
            IProcess process = getProcess();
            if ( process != null ) {
                process.terminate();
            }
        } catch ( VMDisconnectedException e ) {
            // if the VM disconnects while exiting, perform
            // normal termination processing
            terminated();
        } catch ( TimeoutException exception ) {
            // if there is a timeout see if the associated process is terminated
            IProcess process = getProcess();
            if ( process != null && process.isTerminated() ) {
                terminated();
            } else {
                // All we can do is disconnect
                disconnected();
            }
        } catch ( RuntimeException e ) {
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_terminating, e.toString()), e);
        }
    }

    public void handleVMStart(VMStartEvent event) {
        if ( isResumeOnStartup() ) {
            try {
                setSuspended( true );
                resume();
            } catch ( DebugException e ) {
                logError( e );
            }
        }
        // If any threads have resumed since thread collection was initialized,
        // update their status (avoid concurrent modification - use #getThreads())
        IThread[] threads = getThreads();
        for ( int i = 0; i < threads.length; i++ ) {
            DroolsThread thread = (DroolsThread) threads[i];
            if ( thread.isSuspended() ) {
                try {
                    boolean suspended = thread.getUnderlyingThread().isSuspended();
                    if ( !suspended ) {
                        thread.setRunning( true );
                        thread.fireResumeEvent( DebugEvent.CLIENT_REQUEST );
                    }
                } catch ( VMDisconnectedException e ) {
                } catch ( ObjectCollectedException e ) {
                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }
    }

    protected void initializeState() {

        List<ThreadReference> threads = null;
        VirtualMachine vm = getVM();
        if ( vm != null ) {
            try {
                threads = vm.allThreads();
            } catch ( RuntimeException e ) {
                internalError( e );
            }
            if ( threads != null ) {
                Iterator<ThreadReference> initialThreads = threads.iterator();
                while ( initialThreads.hasNext() ) {
                    createThread(initialThreads.next());
                }
            }
        }

        if ( isResumeOnStartup() ) {
            setSuspended( false );
        }
    }

    public void suspend() throws DebugException {
        if ( isSuspended() ) {
            return;
        }
        try {
            VirtualMachine vm = getVM();
            if ( vm != null ) {
                vm.suspend();
            }
            suspendThreads();
            setSuspended( true );
            fireSuspendEvent( DebugEvent.CLIENT_REQUEST );
        } catch ( RuntimeException e ) {
            setSuspended( false );
            fireResumeEvent( DebugEvent.CLIENT_REQUEST );
            targetRequestFailed(MessageFormat.format(JDIDebugModelMessages.JDIDebugTarget_exception_suspend, e.toString()), e);
        }
    }

    public void prepareToSuspendByBreakpoint(JavaBreakpoint breakpoint) {
        setSuspended( true );
        suspendThreads();
    }

    protected void cancelSuspendByBreakpoint(JavaBreakpoint breakpoint) throws DebugException {
        setSuspended( false );
        resumeThreads();
    }

    class MVELTraceHandler
        implements
        IJDIEventListener {

        protected MVELTraceHandler() {
            createRequest();
        }

        protected void createRequest() {
            EventRequestManager manager = getEventRequestManager();
            if ( manager != null ) {
                try {
                    ClassPrepareRequest req = manager.createClassPrepareRequest();
                    req.addClassFilter( "org.drools.core.base.mvel.MVELDebugHandler" );
                    req.setSuspendPolicy( EventRequest.SUSPEND_ALL );
                    addJDIEventListener( MVELTraceHandler.this,
                                         req );
                    req.enable();

                } catch ( RuntimeException e ) {
                    logError( e );
                }
            }
        }

        /**
         * Locates the model thread associated with the underlying JDI thread
         * that has terminated, and removes it from the collection of
         * threads belonging to this debug target. A terminate event is
         * fired for the model thread.
         *
         * @param event a thread death event
         * @param target the target in which the thread died
         * @return <code>true</code> - the thread should be resumed
         */
        public boolean handleEvent(Event event,
                                   JDIDebugTarget target) {
            ClassType classType = ( ClassType ) ((ClassPrepareEvent) event).referenceType();
            String name = classType.name();

            // change this to create a breakpoint, as the method enter was too slow
            BreakpointRequest req = null;
            List<Method> list = classType.methodsByName( "onBreak" );
            if (  list.size() == 0 ) {
                throw new IllegalStateException( "MVELDebugHandler.onBreak cannot be found by JDI" );
            }
            
            Method method = list.get( 0 );
            if (method != null && !method.isNative()) {
                Location location = method.location();
                if (location != null && location.codeIndex() != -1) {
                    req = getEventRequestManager().createBreakpointRequest(location);
                    req.addThreadFilter( ((ClassPrepareEvent) event).thread() );
                    req.setSuspendPolicy( EventRequest.SUSPEND_ALL  );
                } else {
                    throw new IllegalStateException( "MVELDebugHandler.onBreak location cannot be found by JDI" );
                }
            }    else {
                throw new IllegalStateException( "MVELDebugHandler.onBreak cannot be found by JDI" );
            }
            

            //breakpointCatched

            /*field= type.fieldByName(getFieldName());
             Field field;
             EventRequest req= manager.createModificationWatchpointRequest(field);
             */
            //req.setSuspendPolicy( EventRequest.SUSPEND_EVENT_THREAD );
            addJDIEventListener(new IJDIEventListener() {

                public boolean handleEvent(Event event, JDIDebugTarget target) {
                    BreakpointEvent entryEvent = (BreakpointEvent) event;

                    //System.out.println( entryEvent + ":" + entryEvent.location() );

                    try {
                        IThread[] tharr = getThreads();
                        ThreadReference t = null;
                        DroolsThread t2 = null;
                        for (int i = 0; i < tharr.length; i++) {
                            DroolsThread th2 = (DroolsThread) tharr[i];
                            ThreadReference th2real = ((DroolsThread) tharr[i]).getUnderlyingThread();
                            if (th2real.suspendCount() == 1    && th2.getName().equals("main")) {
                                t = th2real;
                                t2 = (DroolsThread) th2;
                                th2real.suspend();
                                th2.setRunning(false);
                                th2.fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
                                return true;
                            }
                        }
                    } catch (Exception t) {
                        logError(t);
                    }
                    return true;
                }

                public void wonSuspendVote(Event event, JDIDebugTarget target) {
                    // do nothing
                }

                public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
                    wonSuspendVote(event, target);
                }

                public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
                    return handleEvent(event, target);
                }

            }, req);

            req.enable();
            
            // now remove the ClassPrepareEvent
            removeJDIEventListener(MVELTraceHandler.this, ((ClassPrepareEvent) event).request() );

            return true;
        }

        /* (non-Javadoc)
         * @see org.eclipse.jdt.internal.debug.core.IJDIEventListener#wonSuspendVote(com.sun.jdi.event.Event, org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget)
         */
        public void wonSuspendVote(Event event,
                                   JDIDebugTarget target) {
            // do nothing
        }

        public void eventSetComplete(Event event, JDIDebugTarget target, boolean suspend, EventSet eventSet) {
            wonSuspendVote(event, target);
        }

        public boolean handleEvent(Event event, JDIDebugTarget target, boolean suspendVote, EventSet eventSet) {
            return handleEvent(event, target);
        }

    }

    /**
     * Tries to find a match for the provided breakpoint information from the list of registered breakpoints.
     * For stepping and possibly other purposes it returns also a breakpoint for cases where exactly the same line was not found.
     * 
     * If breakpoint is not found for <code>line</code> at <code>source</code> then it takes the first line that is above the
     * specified line at the same file.
     * 
     * @param source
     * @param line
     * @return
     */
    public DroolsLineBreakpoint getDroolsBreakpoint(String source) {

        if ( source == null ) {
            return null;
        }

        Iterator<IBreakpoint> iterator = getBreakpoints().iterator();
        while ( iterator.hasNext() ) {
            IJavaBreakpoint element = (IJavaBreakpoint) iterator.next();
            if ( element instanceof DroolsLineBreakpoint && ((DroolsLineBreakpoint) element).getDialectName().equals( "mvel" ) ) {
                DroolsLineBreakpoint l = (DroolsLineBreakpoint) element;
                try {

                    int matchLine = l.getLineNumber();
                    String matchSource = l.getRuleName();

                    if ( source.equals( matchSource ) || l.getFileRuleMappings().containsKey( source ) ) {
                        return l;
                    }

                } catch ( CoreException e ) {
                    logError( e );
                }
            }
        }

        return null;
    }

    private void addRemoteBreakpoint(DroolsLineBreakpoint d) {

        try {
            if ( !d.isEnabled() ) {
                return; // No need to install disabled breakpoints
            }
        } catch ( CoreException e2 ) {
            logError( e2 );
            return; // No need to install breakpoints that are this much broken
        }

        Iterator<ReferenceType> handleriter = getVM().classesByName( "org.drools.core.base.mvel.MVELDebugHandler" ).iterator();
        ReferenceType debugHandlerClass = handleriter.next();

        int line;
        String sourceName;

        try {
            line = d.getLineNumber();
            sourceName = d.getTypeName();
        } catch ( CoreException e1 ) {
            logError( e1 );
            return;
        }

        ReferenceType refType = debugHandlerClass;
        Method m = (Method) refType.methodsByName( "registerBreakpoint" ).iterator().next();
        List<Value> args = new ArrayList<Value>();
        IntegerValue lineVal = getVM().mirrorOf( line );
        StringReference nameVal = getVM().mirrorOf( sourceName );
        JDIObjectValue val = (JDIObjectValue) newValue( sourceName );
        ObjectReference realVal = val.getUnderlyingObject();
        args.add( nameVal );
        args.add( lineVal );

        try {
            ClassType tt = (ClassType) debugHandlerClass;
            IThread[] tharr = getThreads();
            ThreadReference t = null;
            DroolsThread t2 = null;

            for ( int i = 0; i < tharr.length; i++ ) {
                IThread th2 = tharr[i];
                ThreadReference th2real = ((DroolsThread) tharr[i]).getUnderlyingThread();

                if ( th2real.suspendCount() == 1 && th2.getName().equals( "main" ) ) {
                    t = th2real;
                    t2 = (DroolsThread) th2;
                }
            }

            tt.invokeMethod( t,
                             m,
                             args,
                             ObjectReference.INVOKE_SINGLE_THREADED );
            //t2.computeNewStackFrames();

            super.breakpointAdded( d );

        } catch ( Exception e ) {
            logError( e );
        }

    }

    private void removeRemoteBreakpoint(DroolsLineBreakpoint d,
                                        IMarkerDelta delta) {
        Iterator<ReferenceType> handleriter = getVM().classesByName( "org.drools.core.base.mvel.MVELDebugHandler" ).iterator();
        ReferenceType debugHandlerClass = handleriter.next();

        int line;
        String sourceName;

        try {
            line = d.getLineNumber();
            sourceName = d.getTypeName();
        } catch ( CoreException e1 ) {
            logError( e1 );
            return;
        }

        ReferenceType refType = debugHandlerClass;
        Method m = (Method) refType.methodsByName( "removeBreakpoint" ).iterator().next();
        List<Value> args = new ArrayList<Value>();
        IntegerValue lineVal = getVM().mirrorOf( line );
        StringReference nameVal = getVM().mirrorOf( sourceName );
        JDIObjectValue val = (JDIObjectValue) newValue( sourceName );
        ObjectReference realVal = val.getUnderlyingObject();
        args.add( nameVal );
        args.add( lineVal );

        try {
            ClassType tt = (ClassType) debugHandlerClass;
            IThread[] tharr = getThreads();
            ThreadReference t = null;
            DroolsThread t2 = null;

            for ( int i = 0; i < tharr.length; i++ ) {
                IThread th2 = tharr[i];
                ThreadReference th2real = ((DroolsThread) tharr[i]).getUnderlyingThread();

                if ( th2real.suspendCount() == 1 && th2.getName().equals( "main" ) ) {
                    t = th2real;
                    t2 = (DroolsThread) th2;
                }
            }

            tt.invokeMethod( t,
                             m,
                             args,
                             ObjectReference.INVOKE_SINGLE_THREADED );

        } catch ( Exception e ) {
            logError( e );
        }

    }

}
