/*
 * @(#)DragSource.java	1.45 04/05/05
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.awt.dnd;

import java.awt.AWTError;
import java.awt.AWTException;
import java.awt.event.InputEvent;
import java.awt.AWTPermission;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.FlavorMap;
import java.awt.datatransfer.SystemFlavorMap;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.peer.DragSourceContextPeer;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.AccessController;
import java.util.EventListener;
import sun.awt.dnd.SunDragSourceContextPeer;
import sun.security.action.GetIntegerAction;


/**
 * The <code>DragSource</code> is the entity responsible 
 * for the initiation of the Drag
 * and Drop operation, and may be used in a number of scenarios: 
 * <UL>
 * <LI>1 default instance per JVM for the lifetime of that JVM. 
 * <LI>1 instance per class of potential Drag Initiator object (e.g
 * TextField). [implementation dependent] 
 * <LI>1 per instance of a particular 
 * <code>Component</code>, or application specific
 * object associated with a <code>Component</code> 
 * instance in the GUI. [implementation dependent] 
 * <LI>Some other arbitrary association. [implementation dependent] 
 *</UL>
 *
 * Once the <code>DragSource</code> is 
 * obtained, a <code>DragGestureRecognizer</code> should
 * also be obtained to associate the <code>DragSource</code>
 * with a particular
 * <code>Component</code>. 
 * <P>
 * The initial interpretation of the user's gesture, 
 * and the subsequent starting of the drag operation 
 * are the responsibility of the implementing
 * <code>Component</code>, which is usually 
 * implemented by a <code>DragGestureRecognizer</code>. 
 *<P>
 * When a drag gesture occurs, the 
 * <code>DragSource</code>'s 
 * startDrag() method shall be
 * invoked in order to cause processing 
 * of the user's navigational
 * gestures and delivery of Drag and Drop 
 * protocol notifications. A
 * <code>DragSource</code> shall only 
 * permit a single Drag and Drop operation to be
 * current at any one time, and shall 
 * reject any further startDrag() requests
 * by throwing an <code>IllegalDnDOperationException</code> 
 * until such time as the extant operation is complete. 
 * <P>
 * The startDrag() method invokes the 
 * createDragSourceContext() method to
 * instantiate an appropriate 
 * <code>DragSourceContext</code> 
 * and associate the <code>DragSourceContextPeer</code>
 * with that. 
 * <P>
 * If the Drag and Drop System is 
 * unable to initiate a drag operation for
 * some reason, the startDrag() method throws 
 * a <code>java.awt.dnd.InvalidDnDOperationException</code>
 * to signal such a condition. Typically this 
 * exception is thrown when the underlying platform
 * system is either not in a state to 
 * initiate a drag, or the parameters specified are invalid. 
 * <P>
 * Note that during the drag, the 
 * set of operations exposed by the source
 * at the start of the drag operation may not change 
 * until the operation is complete. 
 * The operation(s) are constant for the
 * duration of the operation with respect to the 
 * <code>DragSource</code>. 
 *
 * @version 	1.45, 05/05/04
 * @since 1.2
 */

public class DragSource implements Serializable {

    private static final long serialVersionUID = 6236096958971414066L;

    /*
     * load a system default cursor
     */

    private static Cursor load(String name) {
        if (GraphicsEnvironment.isHeadless()) {
            return null;
        }

	try {
	    return (Cursor)Toolkit.getDefaultToolkit().getDesktopProperty(name);
	} catch (Exception e) {
	    e.printStackTrace();

	    throw new RuntimeException("failed to load system cursor: " + name + " : " + e.getMessage());
	}
    }

   
    /**
     * The default <code>Cursor</code> to use with a copy operation indicating
     * that a drop is currently allowed. <code>null</code> if
     * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
     *
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static final Cursor DefaultCopyDrop =
        load("DnD.Cursor.CopyDrop");

    /**
     * The default <code>Cursor</code> to use with a move operation indicating
     * that a drop is currently allowed. <code>null</code> if
     * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
     *
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static final Cursor DefaultMoveDrop =
        load("DnD.Cursor.MoveDrop");

    /**
     * The default <code>Cursor</code> to use with a link operation indicating
     * that a drop is currently allowed. <code>null</code> if
     * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
     *
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static final Cursor DefaultLinkDrop =
        load("DnD.Cursor.LinkDrop");

    /**
     * The default <code>Cursor</code> to use with a copy operation indicating
     * that a drop is currently not allowed. <code>null</code> if
     * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
     *
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static final Cursor DefaultCopyNoDrop =
        load("DnD.Cursor.CopyNoDrop");

    /**
     * The default <code>Cursor</code> to use with a move operation indicating
     * that a drop is currently not allowed. <code>null</code> if
     * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
     *
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static final Cursor DefaultMoveNoDrop =
        load("DnD.Cursor.MoveNoDrop");

    /**
     * The default <code>Cursor</code> to use with a link operation indicating 
     * that a drop is currently not allowed. <code>null</code> if
     * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>.
     *
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static final Cursor DefaultLinkNoDrop =
        load("DnD.Cursor.LinkNoDrop");

    private static final DragSource dflt =
        (GraphicsEnvironment.isHeadless()) ? null : new DragSource();

    /** 
     * Internal constants for serialization. 
     */
    static final String dragSourceListenerK = "dragSourceL";
    static final String dragSourceMotionListenerK = "dragSourceMotionL";

    /**
     * Gets the <code>DragSource</code> object associated with
     * the underlying platform.
     *
     * @return the platform DragSource
     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
     *            returns true
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public static DragSource getDefaultDragSource() {
        if (GraphicsEnvironment.isHeadless()) {
            throw new HeadlessException();
        } else {
            return dflt;
        }
    }

    /**
     * Reports  
     * whether or not drag 
     * <code>Image</code> support 
     * is available on the underlying platform.
     * <P>
     * @return if the Drag Image support is available on this platform
     */

    public static boolean isDragImageSupported() {
	Toolkit t = Toolkit.getDefaultToolkit();

	Boolean supported;

	try {
	    supported = (Boolean)Toolkit.getDefaultToolkit().getDesktopProperty("DnD.isDragImageSupported");

	    return supported.booleanValue();
	} catch (Exception e) {
	    return false;
	}
    }

    /**
     * Creates a new <code>DragSource</code>.
     *
     * @exception HeadlessException if GraphicsEnvironment.isHeadless()
     *            returns true
     * @see java.awt.GraphicsEnvironment#isHeadless
     */
    public DragSource() throws HeadlessException {
        if (GraphicsEnvironment.isHeadless()) {
            throw new HeadlessException();
        }
    }

    /**
     * Start a drag, given the <code>DragGestureEvent</code> 
     * that initiated the drag, the initial 
     * <code>Cursor</code> to use,
     * the <code>Image</code> to drag, 
     * the offset of the <code>Image</code> origin 
     * from the hotspot of the <code>Cursor</code> at 
     * the instant of the trigger,
     * the <code>Transferable</code> subject data 
     * of the drag, the <code>DragSourceListener</code>, 
     * and the <code>FlavorMap</code>. 
     * <P>
     * @param trigger	     the <code>DragGestureEvent</code> that initiated the drag
     * @param dragCursor     the initial <code>Cursor</code> or <code>null</code> for defaults
     * @param dragImage	     the image to drag or null,
     * @param imageOffset    the offset of the <code>Image</code> origin from the hotspot
     *			     of the <code>Cursor</code> at the instant of the trigger
     * @param transferable   the subject data of the drag
     * @param dsl	     the <code>DragSourceListener</code>
     * @param flavorMap	     the <code>FlavorMap</code> to use, or <code>null</code>
     * <P>
     * @throws <code>java.awt.dnd.InvalidDnDOperationException</code> 
     *    if the Drag and Drop
     *    system is unable to initiate a drag operation, or if the user
     *    attempts to start a drag while an existing drag operation 
     *    is still executing
     */

    public void startDrag(DragGestureEvent   trigger,
			  Cursor	     dragCursor,
			  Image		     dragImage,
			  Point		     imageOffset,
			  Transferable	     transferable,
			  DragSourceListener dsl,
			  FlavorMap	     flavorMap) throws InvalidDnDOperationException {

        SunDragSourceContextPeer.setDragDropInProgress(true);

        try {
            if (flavorMap != null) this.flavorMap = flavorMap;

            DragSourceContextPeer dscp = Toolkit.getDefaultToolkit().createDragSourceContextPeer(trigger);

            DragSourceContext     dsc = createDragSourceContext(dscp,
                                                                trigger,
                                                                dragCursor,
                                                                dragImage,
                                                                imageOffset,
                                                                transferable,
                                                                dsl
                                                                );
            
            if (dsc == null) {
                throw new InvalidDnDOperationException();
            }
							    
            dscp.startDrag(dsc, dsc.getCursor(), dragImage, imageOffset); // may throw
        } catch (RuntimeException e) {
            SunDragSourceContextPeer.setDragDropInProgress(false);
            throw e;
        }
    }

    /**
     * Start a drag, given the <code>DragGestureEvent</code> 
     * that initiated the drag, the initial 
     * <code>Cursor</code> to use,
     * the <code>Transferable</code> subject data 
     * of the drag, the <code>DragSourceListener</code>, 
     * and the <code>FlavorMap</code>. 
     * <P>
     * @param trigger	     the <code>DragGestureEvent</code> that 
     * initiated the drag
     * @param dragCursor     the initial <code>Cursor</code> or 
     * <code>null</code> for defaults
     * @param transferable   the subject data of the drag
     * @param dsl	     the <code>DragSourceListener</code>
     * @param flavorMap	     the <code>FlavorMap</code> to use or <code>null</code>
     * <P>
     * @throws <code>java.awt.dnd.InvalidDnDOperationException</code> 
     *    if the Drag and Drop
     *    system is unable to initiate a drag operation, or if the user
     *    attempts to start a drag while an existing drag operation 
     *    is still executing
     */

    public void startDrag(DragGestureEvent   trigger,
			  Cursor	     dragCursor,
			  Transferable	     transferable,
			  DragSourceListener dsl,
			  FlavorMap	     flavorMap) throws InvalidDnDOperationException {
	startDrag(trigger, dragCursor, null, null, transferable, dsl, flavorMap);
    }

    /**
     * Start a drag, given the <code>DragGestureEvent</code> 
     * that initiated the drag, the initial <code>Cursor</code> 
     * to use,
     * the <code>Image</code> to drag, 
     * the offset of the <code>Image</code> origin 
     * from the hotspot of the <code>Cursor</code>
     * at the instant of the trigger,
     * the subject data of the drag, and 
     * the <code>DragSourceListener</code>. 
     * <P>
     * @param trigger		the <code>DragGestureEvent</code> that initiated the drag
     * @param dragCursor	the initial <code>Cursor</code> or <code>null</code> for defaults
     * @param dragImage		the <code>Image</code> to drag or <code>null</code>
     * @param dragOffset	the offset of the <code>Image</code> origin from the hotspot
     *				of the <code>Cursor</code> at the instant of the trigger
     * @param transferable	the subject data of the drag
     * @param dsl		the <code>DragSourceListener</code>
     * <P>
     * @throws <code>java.awt.dnd.InvalidDnDOperationException</code> 
     *    if the Drag and Drop
     *    system is unable to initiate a drag operation, or if the user
     *    attempts to start a drag while an existing drag operation  
     *    is still executing
     */

    public void startDrag(DragGestureEvent   trigger,
			  Cursor	     dragCursor,
			  Image		     dragImage,
			  Point		     dragOffset,
			  Transferable	     transferable,
			  DragSourceListener dsl) throws InvalidDnDOperationException {
	startDrag(trigger, dragCursor, dragImage, dragOffset, transferable, dsl, null);
    }

    /**
     * Start a drag, given the <code>DragGestureEvent</code> 
     * that initiated the drag, the initial 
     * <code>Cursor</code> to 
     * use, 
     * the <code>Transferable</code> subject data 
     * of the drag, and the <code>DragSourceListener</code>. 
     * <P>
     * @param trigger		the <code>DragGestureEvent</code> that initiated the drag
     * @param dragCursor	the initial <code>Cursor</code> or <code>null</code> for defaults
     * @param transferable	the subject data of the drag
     * @param dsl		the <code>DragSourceListener</code>
     * <P>
     * @throws <code>java.awt.dnd.InvalidDnDOperationException</code> 
     *    if the Drag and Drop
     *    system is unable to initiate a drag operation, or if the user
     *    attempts to start a drag while an existing drag operation 
     *    is still executing
     */

    public void startDrag(DragGestureEvent   trigger,
			  Cursor	     dragCursor,
			  Transferable	     transferable,
			  DragSourceListener dsl) throws InvalidDnDOperationException {
	startDrag(trigger, dragCursor, null, null, transferable, dsl, null);
    }

    /**
     * Creates the <code>DragSourceContext</code> to handle this drag.
     * <p> 
     * To incorporate a new <code>DragSourceContext</code> 
     * subclass, subclass <code>DragSource</code> and
     * override this method.
     * <p>
     * If <code>dragImage</code> is <code>null</code>, no image is used 
     * to represent the drag over feedback for this drag operation, but 
     * <code>NullPointerException</code> is not thrown.
     * <p>
     * If <code>dsl</code> is <code>null</code>, no drag source listener 
     * is registered with the created <code>DragSourceContext</code>,  
     * but <code>NullPointerException</code> is not thrown.
     * <p>
     * If <code>dragCursor</code> is <code>null</code>, the default drag 
     * cursors are used for this drag operation.  
     * <code>NullPointerException</code> is not thrown.
     *
     * @param dscp          The <code>DragSourceContextPeer</code> for this drag
     * @param dgl	    The <code>DragGestureEvent</code> that triggered the 
     *                      drag
     * @param dragCursor    The initial <code>Cursor</code> to display
     * @param dragImage	    The <code>Image</code> to drag or <code>null</code>
     * @param imageOffset   The offset of the <code>Image</code> origin from the 
     *                      hotspot of the cursor at the instant of the trigger
     * @param t             The subject data of the drag
     * @param dsl	    The <code>DragSourceListener</code>
     * 
     * @return the <code>DragSourceContext</code>
     * 
     * @throws NullPointerException if <code>dscp</code> is <code>null</code>
     * @throws NullPointerException if <code>dgl</code> is <code>null</code>
     * @throws NullPointerException if <code>dragImage</code> is not 
     *    <code>null</code> and <code>imageOffset</code> is <code>null</code>
     * @throws NullPointerException if <code>t</code> is <code>null</code>
     * @throws IllegalArgumentException if the <code>Component</code> 
     *         associated with the trigger event is <code>null</code>.
     * @throws IllegalArgumentException if the <code>DragSource</code> for the 
     *         trigger event is <code>null</code>.
     * @throws IllegalArgumentException if the drag action for the
     *         trigger event is <code>DnDConstants.ACTION_NONE</code>.
     * @throws IllegalArgumentException if the source actions for the
     *         <code>DragGestureRecognizer</code> associated with the trigger
     *         event are equal to <code>DnDConstants.ACTION_NONE</code>. 
     */

    protected DragSourceContext createDragSourceContext(DragSourceContextPeer dscp, DragGestureEvent dgl, Cursor dragCursor, Image dragImage, Point imageOffset, Transferable t, DragSourceListener dsl) {
	return new DragSourceContext(dscp, dgl, dragCursor, dragImage, imageOffset, t, dsl);
    }

    /**
     * This method returns the 
     * <code>FlavorMap</code> for this <code>DragSource</code>.
     * <P>
     * @return the <code>FlavorMap</code> for this <code>DragSource</code>
     */

    public FlavorMap getFlavorMap() { return flavorMap; }

    /**
     * Creates a new <code>DragGestureRecognizer</code> 
     * that implements the specified
     * abstract subclass of 
     * <code>DragGestureRecognizer</code>, and 
     * sets the specified <code>Component</code> 
     * and <code>DragGestureListener</code> on 
     * the newly created object.
     * <P>
     * @param recognizerAbstractClass the requested abstract type
     * @param actions		      the permitted source drag actions
     * @param c			      the <code>Component</code> target 
     * @param dgl	 the <code>DragGestureListener</code> to notify
     * <P>
     * @return the new <code>DragGestureRecognizer</code> or <code>null</code>
     *    if the <code>Toolkit.createDragGestureRecognizer</code> method
     *    has no implementation available for 
     *    the requested <code>DragGestureRecognizer</code>
     *    subclass and returns <code>null</code>
     */

    public <T extends DragGestureRecognizer> T
	createDragGestureRecognizer(Class<T> recognizerAbstractClass,
				    Component c, int actions,
				    DragGestureListener dgl)
    {
	return Toolkit.getDefaultToolkit().createDragGestureRecognizer(recognizerAbstractClass, this, c, actions, dgl);
    }

	
    /**
     * Creates a new <code>DragGestureRecognizer</code> 
     * that implements the default
     * abstract subclass of <code>DragGestureRecognizer</code>
     * for this <code>DragSource</code>,
     * and sets the specified <code>Component</code> 
     * and <code>DragGestureListener</code> on the
     * newly created object. 
     *
     * For this <code>DragSource</code> 
     * the default is <code>MouseDragGestureRecognizer</code>.
     * <P>
     * @param c	      the <code>Component</code> target for the recognizer
     * @param actions the permitted source actions
     * @param dgl     the <code>DragGestureListener</code> to notify
     * <P>
     * @return the new <code>DragGestureRecognizer</code> or <code>null</code>
     *    if the <code>Toolkit.createDragGestureRecognizer</code> method
     *    has no implementation available for 
     *    the requested <code>DragGestureRecognizer</code>
     *    subclass and returns <code>null</code>
     */

    public DragGestureRecognizer createDefaultDragGestureRecognizer(Component c, int actions, DragGestureListener dgl) {
	return Toolkit.getDefaultToolkit().createDragGestureRecognizer(MouseDragGestureRecognizer.class, this, c, actions, dgl);
    }

    /**
     * Adds the specified <code>DragSourceListener</code> to this
     * <code>DragSource</code> to receive drag source events during drag
     * operations intiated with this <code>DragSource</code>.
     * If a <code>null</code> listener is specified, no action is taken and no
     * exception is thrown.
     *
     * @param dsl the <code>DragSourceListener</code> to add
     *
     * @see      #removeDragSourceListener
     * @see      #getDragSourceListeners
     * @since 1.4
     */
    public void addDragSourceListener(DragSourceListener dsl) {
	if (dsl != null) {
            synchronized (this) {
                listener = DnDEventMulticaster.add(listener, dsl);
            }
        }
    }

    /**
     * Removes the specified <code>DragSourceListener</code> from this
     * <code>DragSource</code>. 
     * If a <code>null</code> listener is specified, no action is taken and no
     * exception is thrown.
     * If the listener specified by the argument was not previously added to
     * this <code>DragSource</code>, no action is taken and no exception
     * is thrown.
     * 
     * @param dsl the <code>DragSourceListener</code> to remove
     *
     * @see      #addDragSourceListener
     * @see      #getDragSourceListeners
     * @since 1.4
     */
    public void removeDragSourceListener(DragSourceListener dsl) {
        if (dsl != null) {
            synchronized (this) {
                listener = DnDEventMulticaster.remove(listener, dsl);
            }
        }
    }

    /**
     * Gets all the <code>DragSourceListener</code>s
     * registered with this <code>DragSource</code>.
     *
     * @return all of this <code>DragSource</code>'s
     *         <code>DragSourceListener</code>s or an empty array if no
     *         such listeners are currently registered
     *
     * @see      #addDragSourceListener
     * @see      #removeDragSourceListener
     * @since    1.4
     */
    public DragSourceListener[] getDragSourceListeners() {
        return (DragSourceListener[])getListeners(DragSourceListener.class);
    }

    /**
     * Adds the specified <code>DragSourceMotionListener</code> to this
     * <code>DragSource</code> to receive drag motion events during drag
     * operations intiated with this <code>DragSource</code>.
     * If a <code>null</code> listener is specified, no action is taken and no
     * exception is thrown.
     *
     * @param dsml the <code>DragSourceMotionListener</code> to add
     *
     * @see      #removeDragSourceMotionListener
     * @see      #getDragSourceMotionListeners
     * @since 1.4
     */
    public void addDragSourceMotionListener(DragSourceMotionListener dsml) {
	if (dsml != null) {
            synchronized (this) {
                motionListener = DnDEventMulticaster.add(motionListener, dsml);
            }
        }
    }

    /**
     * Removes the specified <code>DragSourceMotionListener</code> from this
     * <code>DragSource</code>. 
     * If a <code>null</code> listener is specified, no action is taken and no
     * exception is thrown.
     * If the listener specified by the argument was not previously added to
     * this <code>DragSource</code>, no action is taken and no exception
     * is thrown.
     * 
     * @param dsml the <code>DragSourceMotionListener</code> to remove
     *
     * @see      #addDragSourceMotionListener
     * @see      #getDragSourceMotionListeners
     * @since 1.4
     */
    public void removeDragSourceMotionListener(DragSourceMotionListener dsml) {
        if (dsml != null) {
            synchronized (this) {
                motionListener = DnDEventMulticaster.remove(motionListener, dsml);
            }
        }
    }

    /**
     * Gets all of the  <code>DragSourceMotionListener</code>s
     * registered with this <code>DragSource</code>.
     *
     * @return all of this <code>DragSource</code>'s
     *         <code>DragSourceMotionListener</code>s or an empty array if no
     *         such listeners are currently registered
     *
     * @see      #addDragSourceMotionListener
     * @see      #removeDragSourceMotionListener
     * @since    1.4
     */
    public DragSourceMotionListener[] getDragSourceMotionListeners() {
        return (DragSourceMotionListener[])
            getListeners(DragSourceMotionListener.class); 
    }

    /**
     * Gets all the objects currently registered as
     * <code><em>Foo</em>Listener</code>s upon this <code>DragSource</code>.
     * <code><em>Foo</em>Listener</code>s are registered using the
     * <code>add<em>Foo</em>Listener</code> method.
     *
     * @param listenerType the type of listeners requested; this parameter
     *          should specify an interface that descends from
     *          <code>java.util.EventListener</code>
     * @return an array of all objects registered as
     *          <code><em>Foo</em>Listener</code>s on this
     *          <code>DragSource</code>, or an empty array if no such listeners
     *          have been added 
     * @exception <code>ClassCastException</code> if <code>listenerType</code>
     *          doesn't specify a class or interface that implements
     *          <code>java.util.EventListener</code>
     *
     * @see #getDragSourceListeners
     * @see #getDragSourceMotionListeners
     * @since 1.4
     */
    public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 
        EventListener l = null; 
        if (listenerType == DragSourceListener.class) { 
            l = listener;
        } else if (listenerType == DragSourceMotionListener.class) {
            l = motionListener;
        }
        return DnDEventMulticaster.getListeners(l, listenerType);
    }

    /**
     * This method calls <code>dragEnter</code> on the
     * <code>DragSourceListener</code>s registered with this
     * <code>DragSource</code>, and passes them the specified
     * <code>DragSourceDragEvent</code>.
     *
     * @param dsde the <code>DragSourceDragEvent</code>
     */
    void processDragEnter(DragSourceDragEvent dsde) {
        DragSourceListener dsl = listener;
        if (dsl != null) {
            dsl.dragEnter(dsde);
        }
    }

    /**
     * This method calls <code>dragOver</code> on the
     * <code>DragSourceListener</code>s registered with this
     * <code>DragSource</code>, and passes them the specified
     * <code>DragSourceDragEvent</code>.
     *
     * @param dsde the <code>DragSourceDragEvent</code>
     */
    void processDragOver(DragSourceDragEvent dsde) {
        DragSourceListener dsl = listener;
        if (dsl != null) {
            dsl.dragOver(dsde);
        }
    }

    /**
     * This method calls <code>dropActionChanged</code> on the
     * <code>DragSourceListener</code>s registered with this
     * <code>DragSource</code>, and passes them the specified
     * <code>DragSourceDragEvent</code>.
     *
     * @param dsde the <code>DragSourceDragEvent</code>
     */
    void processDropActionChanged(DragSourceDragEvent dsde) {
        DragSourceListener dsl = listener;
        if (dsl != null) {
            dsl.dropActionChanged(dsde);
        }
    }

    /**
     * This method calls <code>dragExit</code> on the
     * <code>DragSourceListener</code>s registered with this
     * <code>DragSource</code>, and passes them the specified
     * <code>DragSourceEvent</code>.
     *
     * @param dse the <code>DragSourceEvent</code>
     */
    void processDragExit(DragSourceEvent dse) {
        DragSourceListener dsl = listener;
        if (dsl != null) {
            dsl.dragExit(dse);
        }
    }

    /**
     * This method calls <code>dragDropEnd</code> on the
     * <code>DragSourceListener</code>s registered with this
     * <code>DragSource</code>, and passes them the specified
     * <code>DragSourceDropEvent</code>.
     *
     * @param dsde the <code>DragSourceEvent</code>
     */
    void processDragDropEnd(DragSourceDropEvent dsde) {
        DragSourceListener dsl = listener;
        if (dsl != null) {
            dsl.dragDropEnd(dsde);
        }
    }

    /**
     * This method calls <code>dragMouseMoved</code> on the
     * <code>DragSourceMotionListener</code>s registered with this
     * <code>DragSource</code>, and passes them the specified
     * <code>DragSourceDragEvent</code>.
     *
     * @param dsde the <code>DragSourceEvent</code>
     */
    void processDragMouseMoved(DragSourceDragEvent dsde) {
        DragSourceMotionListener dsml = motionListener;
        if (dsml != null) {
            dsml.dragMouseMoved(dsde);
        }
    }

    /**
     * Serializes this <code>DragSource</code>. This method first performs
     * default serialization. Next, it writes out this object's
     * <code>FlavorMap</code> if and only if it can be serialized. If not,
     * <code>null</code> is written instead. Next, it writes out
     * <code>Serializable</code> listeners registered with this
     * object. Listeners are written in a <code>null</code>-terminated sequence
     * of 0 or more pairs. The pair consists of a <code>String</code> and an
     * <code>Object</code>; the <code>String</code> indicates the type of the
     * <code>Object</code> and is one of the following:
     * <ul>
     * <li><code>dragSourceListenerK</code> indicating a
     *     <code>DragSourceListener</code> object;
     * <li><code>dragSourceMotionListenerK</code> indicating a
     *     <code>DragSourceMotionListener</code> object.
     * </ul>
     *
     * @serialData Either a <code>FlavorMap</code> instance, or
     *      <code>null</code>, followed by a <code>null</code>-terminated
     *      sequence of 0 or more pairs; the pair consists of a
     *      <code>String</code> and an <code>Object</code>; the
     *      <code>String</code> indicates the type of the <code>Object</code>
     *      and is one of the following:
     *      <ul>
     *      <li><code>dragSourceListenerK</code> indicating a
     *          <code>DragSourceListener</code> object;
     *      <li><code>dragSourceMotionListenerK</code> indicating a
     *          <code>DragSourceMotionListener</code> object.
     *      </ul>.
     * @since 1.4
     */
    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();

        s.writeObject(SerializationTester.test(flavorMap) ? flavorMap : null);

        DnDEventMulticaster.save(s, dragSourceListenerK, listener);
        DnDEventMulticaster.save(s, dragSourceMotionListenerK, motionListener);
        s.writeObject(null);
    }

    /**
     * Deserializes this <code>DragSource</code>. This method first performs
     * default deserialization. Next, this object's <code>FlavorMap</code> is 
     * deserialized by using the next object in the stream.
     * If the resulting <code>FlavorMap</code> is <code>null</code>, this
     * object's <code>FlavorMap</code> is set to the default FlavorMap for
     * this thread's <code>ClassLoader</code>.
     * Next, this object's listeners are deserialized by reading a
     * <code>null</code>-terminated sequence of 0 or more key/value pairs
     * from the stream: 
     * <ul>
     * <li>If a key object is a <code>String</code> equal to
     * <code>dragSourceListenerK</code>, a <code>DragSourceListener</code> is 
     * deserialized using the corresponding value object and added to this
     * <code>DragSource</code>. 
     * <li>If a key object is a <code>String</code> equal to
     * <code>dragSourceMotionListenerK</code>, a
     * <code>DragSourceMotionListener</code> is deserialized using the
     * corresponding value object and added to this <code>DragSource</code>.
     * <li>Otherwise, the key/value pair is skipped.
     * </ul>
     *
     * @see java.awt.datatransfer.SystemFlavorMap#getDefaultFlavorMap
     * @since 1.4
     */
    private void readObject(ObjectInputStream s) 
      throws ClassNotFoundException, IOException {
        s.defaultReadObject();

        // 'flavorMap' was written explicitly
        flavorMap = (FlavorMap)s.readObject();

        // Implementation assumes 'flavorMap' is never null.
        if (flavorMap == null) {
            flavorMap = SystemFlavorMap.getDefaultFlavorMap();
        }

        Object keyOrNull;
        while (null != (keyOrNull = s.readObject())) {
            String key = ((String)keyOrNull).intern();

            if (dragSourceListenerK == key) {
                addDragSourceListener((DragSourceListener)(s.readObject()));
            } else if (dragSourceMotionListenerK == key) {
                addDragSourceMotionListener(
                    (DragSourceMotionListener)(s.readObject()));
            } else { 
                // skip value for unrecognized key
                s.readObject();
            }
        }
    }

    /**
     * Returns the drag gesture motion threshold. The drag gesture motion threshold
     * defines the recommended behavior for {@link MouseDragGestureRecognizer}s.
     * <p>
     * If the system property <code>awt.dnd.drag.threshold</code> is set to 
     * a positive integer, this method returns the value of the system property;
     * otherwise if a pertinent desktop property is available and supported by
     * the implementation of the Java platform, this method returns the value of
     * that property; otherwise this method returns some default value.
     * The pertinent desktop property can be queried using
     * <code>java.awt.Toolkit.getDesktopProperty("DnD.gestureMotionThreshold")</code>.
     *
     * @return the drag gesture motion threshold
     * @see MouseDragGestureRecognizer
     * @since 1.5
     */
    public static int getDragThreshold() {
        int ts = ((Integer)AccessController.doPrivileged(
                new GetIntegerAction("awt.dnd.drag.threshold", 0))).intValue();
        if (ts > 0) {
            return ts;
        } else {
            Integer td = (Integer)Toolkit.getDefaultToolkit().
                    getDesktopProperty("DnD.gestureMotionThreshold");
            if (td != null) {
                return td.intValue();
            }
        }
        return 5;
    }

    /*
     * fields
     */

    private transient FlavorMap flavorMap = SystemFlavorMap.getDefaultFlavorMap();

    private transient DragSourceListener listener;

    private transient DragSourceMotionListener motionListener;
}
