/*
 * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

//#define DND_DEBUG TRUE

#import "CDropTarget.h"
#import "AWTView.h"

#import "sun_lwawt_macosx_CDropTarget.h"
#import "java_awt_dnd_DnDConstants.h"

#import <JavaRuntimeSupport/JavaRuntimeSupport.h>
#include <objc/objc-runtime.h>


#import "CDragSource.h"
#import "CDataTransferer.h"
#import "DnDUtilities.h"
#import "ThreadUtilities.h"
#import "JNIUtilities.h"


static NSInteger        sDraggingSequenceNumber = -1;
static NSDragOperation    sDragOperation;
static NSDragOperation    sUpdateOperation;
static jint                sJavaDropOperation;
static NSPoint            sDraggingLocation;
static BOOL                sDraggingExited;
static BOOL                sDraggingError;

static NSUInteger        sPasteboardItemsCount = 0;
static NSArray*            sPasteboardTypes = nil;
static NSArray*            sPasteboardData = nil;
static jlongArray        sDraggingFormats = nil;

static CDropTarget*        sCurrentDropTarget;

extern jclass jc_CDropTargetContextPeer;
#define GET_DTCP_CLASS() \
    GET_CLASS(jc_CDropTargetContextPeer, "sun/lwawt/macosx/CDropTargetContextPeer");

#define GET_DTCP_CLASS_RETURN(ret) \
    GET_CLASS_RETURN(jc_CDropTargetContextPeer, "sun/lwawt/macosx/CDropTargetContextPeer", ret);

@implementation CDropTarget

+ (CDropTarget *) currentDropTarget {
    return sCurrentDropTarget;
}

- (id)init:(jobject)jdropTarget component:(jobject)jcomponent control:(id)control
{
    self = [super init];
    DLog2(@"[CDropTarget init]: %@\n", self);

    fView = nil;
    fComponent = nil;
    fDropTarget = nil;
    fDropTargetContextPeer = nil;


    if (control != nil) {
        JNIEnv *env = [ThreadUtilities getJNIEnvUncached];
        fComponent = (*env)->NewGlobalRef(env, jcomponent);
        fDropTarget = (*env)->NewGlobalRef(env, jdropTarget);

        fView = [((AWTView *) control) retain];
        [fView setDropTarget:self];


    } else {
        // This would be an error.
        [self release];
        self = nil;
    }
    return self;
}

// When [CDropTarget init] is called the ControlModel's fView may not have been set up yet. ControlModel
// (soon after) calls [CDropTarget controlModelControlValid] on the native event thread, once per CDropTarget,
// to let it know it's been set up now.
- (void)controlModelControlValid
{
    // 9-30-02 Note: [Radar 3065621]
    // List all known pasteboard types here (see AppKit's NSPasteboard.h)
    // How to register for non-standard data types remains to be determined.
    NSArray* dataTypes = [[NSArray alloc] initWithObjects:
        NSStringPboardType,
        NSFilenamesPboardType,
        NSPostScriptPboardType,
        NSTIFFPboardType,
        NSPasteboardTypePNG,
        NSRTFPboardType,
        NSTabularTextPboardType,
        NSFontPboardType,
        NSRulerPboardType,
        NSFileContentsPboardType,
        NSColorPboardType,
        NSRTFDPboardType,
        NSHTMLPboardType,
        NSURLPboardType,
        NSPDFPboardType,
        NSVCardPboardType,
        NSFilesPromisePboardType,
        [DnDUtilities javaPboardType],
        (NSString*)kUTTypeJPEG,
        nil];

    // Enable dragging events over this object:
    [fView registerForDraggedTypes:dataTypes];

    [dataTypes release];
}

- (void)releaseDraggingData
{
    DLog2(@"[CDropTarget releaseDraggingData]: %@\n", self);

    // Release any old pasteboard types, data and properties:
    [sPasteboardTypes release];
    sPasteboardTypes = nil;

    [sPasteboardData release];
    sPasteboardData = nil;

    if (sDraggingFormats != NULL) {
        JNIEnv *env = [ThreadUtilities getJNIEnv];
        (*env)->DeleteGlobalRef(env, sDraggingFormats);
        sDraggingFormats = NULL;
    }

    sPasteboardItemsCount = 0;
    sDraggingSequenceNumber = -1;
}

- (void)removeFromView:(JNIEnv *)env
{
    DLog2(@"[CDropTarget removeFromView]: %@\n", self);

    // Remove this dragging destination from the view:
    [((AWTView *) fView) setDropTarget:nil];

    // Clean up JNI refs
    if (fComponent != NULL) {
        (*env)->DeleteGlobalRef(env, fComponent);
        fComponent = NULL;
    }
    if (fDropTarget != NULL) {
        (*env)->DeleteGlobalRef(env, fDropTarget);
        fDropTarget = NULL;
    }
    if (fDropTargetContextPeer != NULL) {
        (*env)->DeleteGlobalRef(env, fDropTargetContextPeer);
        fDropTargetContextPeer = NULL;
    }

    [self release];
}

- (void)dealloc
{
    DLog2(@"[CDropTarget dealloc]: %@\n", self);

    if(sCurrentDropTarget == self) {
        sCurrentDropTarget = nil;
    }

    [fView release];
    fView = nil;

    [super dealloc];
}

- (NSInteger) getDraggingSequenceNumber
{
    return sDraggingSequenceNumber;
}

// Debugging help:
- (void)dumpPasteboard:(NSPasteboard*)pasteboard
{
    NSArray* pasteboardTypes = [pasteboard types];
    NSUInteger pasteboardItemsCount = [pasteboardTypes count];
    NSUInteger i;

    // For each flavor on the pasteboard show the type, its data, and its property if there is one:
    for (i = 0; i < pasteboardItemsCount; i++) {
        NSString* pbType = [pasteboardTypes objectAtIndex:i];
        CFShow(pbType);

        NSData*    pbData = [pasteboard dataForType:pbType];
        CFShow(pbData);

        if ([pbType hasPrefix:@"CorePasteboardFlavorType"] == NO) {
            id pbDataProperty = [pasteboard propertyListForType:pbType];
            CFShow(pbDataProperty);
        }
    }
}

- (BOOL)copyDraggingTypes:(id<NSDraggingInfo>)sender
{
    DLog2(@"[CDropTarget copyDraggingTypes]: %@\n", self);
    JNIEnv*    env = [ThreadUtilities getJNIEnv];

    // Release any old pasteboard data:
    [self releaseDraggingData];

    NSPasteboard* pb = [sender draggingPasteboard];
    sPasteboardTypes = [[pb types] retain];
    sPasteboardItemsCount = [sPasteboardTypes count];
    if (sPasteboardItemsCount == 0)
        return FALSE;

    jlongArray formats = (*env)->NewLongArray(env, sPasteboardItemsCount);
    if (formats == nil)
        return FALSE;

    sDraggingFormats = (jlongArray) (*env)->NewGlobalRef(env, formats);
    (*env)->DeleteLocalRef(env, formats);
    if (sDraggingFormats == nil)
        return FALSE;

    jboolean isCopy;
    jlong* jformats = (*env)->GetLongArrayElements(env, sDraggingFormats, &isCopy);
    if (jformats == nil) {
        return FALSE;
    }

    // Copy all data formats and properties. In case of properties, if they are nil, we need to use
    // a special NilProperty since [NSArray addObject] would crash on adding a nil object.
    DLog2(@"[CDropTarget copyDraggingTypes]: typesCount = %lu\n", (unsigned long) sPasteboardItemsCount);
    NSUInteger i;
    for (i = 0; i < sPasteboardItemsCount; i++) {
        NSString* pbType = [sPasteboardTypes objectAtIndex:i];
        DLog3(@"[CDropTarget copyDraggingTypes]: type[%lu] = %@\n", (unsigned long) i, pbType);

        // 01-10-03 Note: until we need data properties for doing something useful don't copy them.
        // They're often copies of their flavor's data and copying them for all available pasteboard flavors
        // (which are often auto-translation of one another) can be a significant time/space hit.

        // If this is a remote object type (not a pre-defined format) register it with the pasteboard:
        jformats[i] = indexForFormat(pbType);
        if (jformats[i] == -1 && [pbType hasPrefix:@"JAVA_DATAFLAVOR:application/x-java-remote-object;"])
            jformats[i] = registerFormatWithPasteboard(pbType);
    }

    (*env)->ReleaseLongArrayElements(env, sDraggingFormats, jformats, 0);

    return TRUE;
}

- (BOOL)copyDraggingData:(id<NSDraggingInfo>)sender
{
    DLog2(@"[CDropTarget copyDraggingData]: %@\n", self);

    sPasteboardData = [[NSMutableArray alloc] init];
    if (sPasteboardData == nil)
        return FALSE;

    // Copy all data items to a safe place since the pasteboard may go away before we'll need them:
    NSPasteboard* pb = [sender draggingPasteboard];
    NSUInteger i;
    for (i = 0; i < sPasteboardItemsCount; i++) {
        // Get a type and its data and save the data:
        NSString* pbType = [sPasteboardTypes objectAtIndex:i];
        // 01-10-03 Note: copying only NS-type data (until Java-specified types can make it through the AppKit)
        // would be a good idea since we can't do anything with those CoreFoundation unknown types anyway.
        // But I'm worried that it would break something in Fuller so I'm leaving this here as a reminder,
        // to be evaluated later.
        //id pbData = [pbType hasPrefix:@"NS"] ? [pb dataForType:pbType] : nil; // Copy only NS-type data!
        id pbData = [pb dataForType:pbType];

        // If the data is null we can't store it in the array - an exception would be thrown.
        // We use the special object NSNull instead which is kosher.
        if (pbData == nil)
            pbData = [NSNull null];

        [((NSMutableArray*) sPasteboardData) addObject:pbData];
    }

    return TRUE;
}

- (NSData*) getDraggingDataForURL:(NSData*)data
{
    NSData* result = nil;

    // Convert data into a property list if possible:
    NSPropertyListFormat propertyListFormat;
    NSString* errorString = nil;
    id propertyList = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable
        format:&propertyListFormat errorDescription:&errorString];

    // URL types have only a single URL string in an array:
    if (propertyList != nil && errorString == nil && [propertyList isKindOfClass:[NSArray class]]) {
        NSArray*  array = (NSArray*) propertyList;
        if ([array count] > 0) {
            NSString* url = (NSString*) [array objectAtIndex:0];
            if (url != nil && [url length] > 0)
                result = [url dataUsingEncoding:[url fastestEncoding]];
        }
    }

    return result;
}

- (jobject) copyDraggingDataForFormat:(jlong)format
{
    JNIEnv*      env = [ThreadUtilities getJNIEnvUncached]; // Join the main thread by requesting uncached environment

    NSData*      data = nil;

    // Convert the Java format (datatransferer int index) to a pasteboard format (NSString):
    NSString* pbType = formatForIndex(format);
    if ([sPasteboardTypes containsObject:pbType]) {
        NSUInteger dataIndex = [sPasteboardTypes indexOfObject:pbType];
        data = [sPasteboardData objectAtIndex:dataIndex];

        if ((id) data == [NSNull null])
            data = nil;

        // format == 8 (CF_URL in CDataTransferer): we need a URL-to-String conversion:
        else if ([pbType isEqualToString:@"Apple URL pasteboard type"])
            data = [self getDraggingDataForURL:data];
    }

    // Get NS data:
    char* dataBytes = (data != nil) ? (char*) [data bytes] : "Unsupported type";
    NSUInteger dataLength = (data != nil) ? [data length] : sizeof("Unsupported type");

    // Create a global byte array:
    jbyteArray lbyteArray = (*env)->NewByteArray(env, dataLength);
    if (lbyteArray == nil)
        return nil;
    jbyteArray gbyteArray = (jbyteArray) (*env)->NewGlobalRef(env, lbyteArray);
    (*env)->DeleteLocalRef(env, lbyteArray);
    if (gbyteArray == nil)
        return nil;

    // Get byte array elements:
    jboolean isCopy;
    jbyte* jbytes = (*env)->GetByteArrayElements(env, gbyteArray, &isCopy);
    if (jbytes == nil)
        return nil;

    // Copy data to byte array and release elements:
    memcpy(jbytes, dataBytes, dataLength);
    (*env)->ReleaseByteArrayElements(env, gbyteArray, jbytes, 0);

    // In case of an error make sure to return nil:
    if ((*env)->ExceptionCheck(env)) {
        (*env)->ExceptionDescribe(env);
        gbyteArray = nil;
    }

    return gbyteArray;
}

- (void)safeReleaseDraggingData:(NSNumber *)arg
{
    jlong draggingSequenceNumber = [arg longLongValue];

    // Make sure dragging data is released only if no new drag is under way. If a new drag
    // has been initiated it has released the old dragging data already. This has to be called
    // on the native event thread - otherwise we'd need to start synchronizing.
    if (draggingSequenceNumber == sDraggingSequenceNumber)
        [self releaseDraggingData];
}

- (void)javaDraggingEnded:(jlong)draggingSequenceNumber success:(BOOL)jsuccess action:(jint)jdropaction
{
    NSNumber *draggingSequenceNumberID = [NSNumber numberWithLongLong:draggingSequenceNumber];
        // Report back actual Swing success, not what AppKit thinks
        sDraggingError = !jsuccess;
        sDragOperation = [DnDUtilities mapJavaDragOperationToNS:jdropaction];

    // Release dragging data if any when Java's AWT event thread is all finished.
    // Make sure dragging data is released on the native event thread.
    [ThreadUtilities performOnMainThread:@selector(safeReleaseDraggingData:) on:self withObject:draggingSequenceNumberID waitUntilDone:NO];
}

- (jint)currentJavaActions {
    return [DnDUtilities mapNSDragOperationToJava:sUpdateOperation];
}

/********************************  BEGIN NSDraggingDestination Interface  ********************************/


// Private API to calculate the current Java actions
- (void) calculateCurrentSourceActions:(jint *)actions dropAction:(jint *)dropAction
{
    // Get the raw (unmodified by keys) source actions
    id jrsDrag = objc_lookUpClass("JRSDrag");
    if (jrsDrag != nil) {
        NSDragOperation rawDragActions = (NSDragOperation) [jrsDrag performSelector:@selector(currentAllowableActions)];
        if (rawDragActions != NSDragOperationNone) {
            // Both actions and dropAction default to the rawActions
            *actions = [DnDUtilities mapNSDragOperationMaskToJava:rawDragActions];
            *dropAction = *actions;

            // Get the current key modifiers.
            NSUInteger dragModifiers = (NSUInteger) [jrsDrag performSelector:@selector(currentModifiers)];
            // Either the drop action is narrowed as per Java rules (MOVE, COPY, LINK, NONE) or by the drag modifiers
            if (dragModifiers) {
                // Get the user selected operation based on the drag modifiers, then return the intersection
                NSDragOperation currentOp = [DnDUtilities nsDragOperationForModifiers:dragModifiers];
                NSDragOperation allowedOp = rawDragActions & currentOp;

                *dropAction = [DnDUtilities mapNSDragOperationToJava:allowedOp];
            }
        }
    }
    *dropAction = [DnDUtilities narrowJavaDropActions:*dropAction];
}

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
    DLog2(@"[CDropTarget draggingEntered]: %@\n", self);

    sCurrentDropTarget = self;

    JNIEnv* env = [ThreadUtilities getJNIEnv];
    NSInteger draggingSequenceNumber = [sender draggingSequenceNumber];

    // Set the initial drag operation return value:
    NSDragOperation dragOp = NSDragOperationNone;
        sJavaDropOperation = java_awt_dnd_DnDConstants_ACTION_NONE;

    // We could probably special-case some stuff if drag and drop objects match:
    //if ([sender dragSource] == fView)

    if (draggingSequenceNumber != sDraggingSequenceNumber) {
        sDraggingSequenceNumber = draggingSequenceNumber;
        sDraggingError = FALSE;

        // Delete any drop target context peer left over from a previous drag:
        if (fDropTargetContextPeer != NULL) {
            (*env)->DeleteGlobalRef(env, fDropTargetContextPeer);
            fDropTargetContextPeer = NULL;
        }

        // Look up the CDropTargetContextPeer class:
        GET_DTCP_CLASS_RETURN(dragOp);
        DECLARE_STATIC_METHOD_RETURN(getDropTargetContextPeerMethod, jc_CDropTargetContextPeer,
                                     "getDropTargetContextPeer", "()Lsun/lwawt/macosx/CDropTargetContextPeer;", dragOp)
        if (sDraggingError == FALSE) {
            // Create a new drop target context peer:
            jobject dropTargetContextPeer = (*env)->CallStaticObjectMethod(env, jc_CDropTargetContextPeer, getDropTargetContextPeerMethod);
            CHECK_EXCEPTION();

            if (dropTargetContextPeer != nil) {
                fDropTargetContextPeer = (*env)->NewGlobalRef(env, dropTargetContextPeer);
                (*env)->DeleteLocalRef(env, dropTargetContextPeer);
            }
        }

        // Get dragging types (dragging data is only copied if dropped):
        if (sDraggingError == FALSE && [self copyDraggingTypes:sender] == FALSE)
            sDraggingError = TRUE;
    }

    if (sDraggingError == FALSE) {
        sDraggingExited = FALSE;
        sDraggingLocation = [sender draggingLocation];
        NSPoint javaLocation = [fView convertPoint:sDraggingLocation fromView:nil];
        javaLocation.y = fView.window.frame.size.height - javaLocation.y;

        DLog5(@"+ dragEnter: loc native %f, %f, java %f, %f\n", sDraggingLocation.x, sDraggingLocation.y, javaLocation.x, javaLocation.y);

                ////////// BEGIN Calculate the current drag actions //////////
                jint actions = java_awt_dnd_DnDConstants_ACTION_NONE;
        jint dropAction = actions;

                [self calculateCurrentSourceActions:&actions dropAction:&dropAction];

                sJavaDropOperation = dropAction;
                ////////// END Calculate the current drag actions //////////

        jlongArray formats = sDraggingFormats;

        GET_DTCP_CLASS_RETURN(dragOp);
        DECLARE_METHOD_RETURN(handleEnterMessageMethod, jc_CDropTargetContextPeer,
                              "handleEnterMessage", "(Ljava/awt/Component;IIII[JJ)I", dragOp);
        if (sDraggingError == FALSE) {
            // Double-casting self gets rid of 'different size' compiler warning:
            // AWT_THREADING Safe (CToolkitThreadBlockedHandler)
            actions = (*env)->CallIntMethod(env, fDropTargetContextPeer, handleEnterMessageMethod,
                                       fComponent, (jint) javaLocation.x, (jint) javaLocation.y,
                                       dropAction, actions, formats, ptr_to_jlong(self));
            CHECK_EXCEPTION();
        }

        if (sDraggingError == FALSE) {
            // Initialize drag operation:
            sDragOperation = NSDragOperationNone;

            // Map Java actions back to NSDragOperation.
            // 1-6-03 Note: if the entry point of this CDropTarget isn't covered by a droppable component
            // (as can be the case with lightweight children) we must not return NSDragOperationNone
            // since that would prevent dropping into any of the contained drop targets.
            // Unfortunately there is no easy way to test this so we just test actions and override them
            // with GENERIC if necessary. Proper drag operations will be returned by draggingUpdated: which is
            // called right away, taking care of setting the right cursor and snap-back action.
            dragOp = ((actions != java_awt_dnd_DnDConstants_ACTION_NONE) ?
                [DnDUtilities mapJavaDragOperationToNS:dropAction] : NSDragOperationGeneric);

            // Remember the dragOp for no-op'd update messages:
            sUpdateOperation = dragOp;
        }
    }

    // 9-11-02 Note: the native event thread would not handle an exception gracefully:
    //if (sDraggingError == TRUE)
    //    [NSException raise:NSGenericException format:@"[CDropTarget draggingEntered] failed."];

    DLog2(@"[CDropTarget draggingEntered]: returning %lu\n", (unsigned long) dragOp);

    return dragOp;
}

- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
    //DLog2(@"[CDropTarget draggingUpdated]: %@\n", self);

    sCurrentDropTarget = self;

    // Set the initial drag operation return value:
    NSDragOperation dragOp = (sDraggingError == FALSE ? sUpdateOperation : NSDragOperationNone);

    // There are two things we would be interested in:
    // a) mouse pointer has moved
    // b) drag actions (key modifiers) have changed

    NSPoint draggingLocation = [sender draggingLocation];
    JNIEnv* env = [ThreadUtilities getJNIEnv];

    BOOL notifyJava = FALSE;

    // a) mouse pointer has moved:
    if (NSEqualPoints(draggingLocation, sDraggingLocation) == FALSE) {
        //DLog2(@"[CDropTarget draggingUpdated]: mouse moved, %@\n", self);
        sDraggingLocation = draggingLocation;
        notifyJava = TRUE;
    }

    // b) drag actions (key modifiers) have changed (handleMotionMessage() will do proper notifications):
        ////////// BEGIN Calculate the current drag actions //////////
        jint actions = java_awt_dnd_DnDConstants_ACTION_NONE;
        jint dropAction = actions;

        [self calculateCurrentSourceActions:&actions dropAction:&dropAction];

        if (sJavaDropOperation != dropAction) {
            sJavaDropOperation = dropAction;
            notifyJava = TRUE;
        }
        ////////// END Calculate the current drag actions //////////

    jint userAction = dropAction;

    // Should we notify Java things have changed?
    if (sDraggingError == FALSE && notifyJava) {
        NSPoint javaLocation = [fView convertPoint:sDraggingLocation fromView:nil];
        javaLocation.y = fView.window.frame.size.height - javaLocation.y;
        //DLog5(@"  : dragMoved: loc native %f, %f, java %f, %f\n", sDraggingLocation.x, sDraggingLocation.y, javaLocation.x, javaLocation.y);

        jlongArray formats = sDraggingFormats;

        GET_DTCP_CLASS_RETURN(dragOp);
        DECLARE_METHOD_RETURN(handleMotionMessageMethod, jc_CDropTargetContextPeer, "handleMotionMessage", "(Ljava/awt/Component;IIII[JJ)I", dragOp);
        if (sDraggingError == FALSE) {
            DLog3(@"  >> posting handleMotionMessage, point %f, %f", javaLocation.x, javaLocation.y);
            userAction = (*env)->CallIntMethod(env, fDropTargetContextPeer, handleMotionMessageMethod, fComponent,
                         (jint) javaLocation.x, (jint) javaLocation.y, dropAction, actions, formats, ptr_to_jlong(self)); // AWT_THREADING Safe (CToolkitThreadBlockedHandler)
        CHECK_EXCEPTION();
        }

        if (sDraggingError == FALSE) {
            dragOp = [DnDUtilities mapJavaDragOperationToNS:userAction];

            // Remember the dragOp for no-op'd update messages:
            sUpdateOperation = dragOp;
        } else {
            dragOp = NSDragOperationNone;
        }
    }

    DLog2(@"[CDropTarget draggingUpdated]: returning %lu\n", (unsigned long) dragOp);

    return dragOp;
}

- (void)draggingExited:(id<NSDraggingInfo>)sender
{
    DLog2(@"[CDropTarget draggingExited]: %@\n", self);

    sCurrentDropTarget = nil;

    JNIEnv* env = [ThreadUtilities getJNIEnv];

    if (sDraggingExited == FALSE && sDraggingError == FALSE) {
        GET_DTCP_CLASS();
        DECLARE_METHOD(handleExitMessageMethod, jc_CDropTargetContextPeer, "handleExitMessage", "(Ljava/awt/Component;J)V");
        if (sDraggingError == FALSE) {
            DLog3(@"  - dragExit: loc native %f, %f\n", sDraggingLocation.x, sDraggingLocation.y);
             // AWT_THREADING Safe (CToolkitThreadBlockedHandler)
            (*env)->CallVoidMethod(env, fDropTargetContextPeer,
                              handleExitMessageMethod, fComponent, ptr_to_jlong(self));
            CHECK_EXCEPTION();
        }

        // 5-27-03 Note: [Radar 3270455]
        // -draggingExited: can be called both by the AppKit and by -performDragOperation: but shouldn't execute
        // twice per drop since cleanup code like that in swing/plaf/basic/BasicDropTargetListener would throw NPEs.
        sDraggingExited = TRUE;
    }

    DLog(@"[CDropTarget draggingExited]: returning.\n");
}

- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
    DLog2(@"[CDropTarget prepareForDragOperation]: %@\n", self);
    DLog2(@"[CDropTarget prepareForDragOperation]: returning %@\n", (sDraggingError ? @"NO" : @"YES"));

    return sDraggingError ? NO : YES;
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
    DLog2(@"[CDropTarget performDragOperation]: %@\n", self);

    sCurrentDropTarget = nil;

    JNIEnv* env = [ThreadUtilities getJNIEnv];

    // Now copy dragging data:
    if (sDraggingError == FALSE && [self copyDraggingData:sender] == FALSE)
        sDraggingError = TRUE;

    if (sDraggingError == FALSE) {
        sDraggingLocation = [sender draggingLocation];
        NSPoint javaLocation = [fView convertPoint:sDraggingLocation fromView:nil];
        // The y coordinate that comes in the NSDraggingInfo seems to be reversed - probably
        // has to do something with the type of view it comes to.
        // This is the earliest place where we can correct it.
        javaLocation.y = fView.window.frame.size.height - javaLocation.y;

        jint actions = [DnDUtilities mapNSDragOperationMaskToJava:[sender draggingSourceOperationMask]];
        jint dropAction = sJavaDropOperation;

        jlongArray formats = sDraggingFormats;

        GET_DTCP_CLASS_RETURN(NO);
        DECLARE_METHOD_RETURN(handleDropMessageMethod, jc_CDropTargetContextPeer, "handleDropMessage", "(Ljava/awt/Component;IIII[JJ)V", NO);

        if (sDraggingError == FALSE) {
            (*env)->CallVoidMethod(env, fDropTargetContextPeer, handleDropMessageMethod, fComponent,
                     (jint) javaLocation.x, (jint) javaLocation.y, dropAction, actions, formats, ptr_to_jlong(self)); // AWT_THREADING Safe (event)
            CHECK_EXCEPTION();
        }
    } else {
        // 8-19-03 Note: [Radar 3368754]
        // draggingExited: is not called after a drop - we must do that here ... but only in case
        // of an error, instead of drop(). Otherwise we get twice the cleanup in shared code.
        [self draggingExited:sender];
    }

// TODO:BG
//   [(id)sender _setLastDragDestinationOperation:sDragOperation];


    DLog2(@"[CDropTarget performDragOperation]: returning %@\n", (sDraggingError ? @"NO" : @"YES"));

    return !sDraggingError;
}

- (void)concludeDragOperation:(id<NSDraggingInfo>)sender
{
    sCurrentDropTarget = nil;

    DLog2(@"[CDropTarget concludeDragOperation]: %@\n", self);
    DLog(@"[CDropTarget concludeDragOperation]: returning.\n");
}

// 9-11-02 Note: draggingEnded is not yet implemented by the AppKit.
- (void)draggingEnded:(id<NSDraggingInfo>)sender
{
    sCurrentDropTarget = nil;

    DLog2(@"[CDropTarget draggingEnded]: %@\n", self);
    DLog(@"[CDropTarget draggingEnded]: returning.\n");
}

/********************************  END NSDraggingDestination Interface  ********************************/

@end


/*
 * Class:     sun_lwawt_macosx_CDropTarget
 * Method:    createNativeDropTarget
 * Signature: (Ljava/awt/dnd/DropTarget;Ljava/awt/Component;Ljava/awt/peer/ComponentPeer;J)J
 */
JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CDropTarget_createNativeDropTarget
  (JNIEnv *env, jobject jthis, jobject jdroptarget, jobject jcomponent, jlong jnativepeer)
{
    CDropTarget* dropTarget = nil;

JNI_COCOA_ENTER(env);
    id controlObj = (id) jlong_to_ptr(jnativepeer);
    dropTarget = [[CDropTarget alloc] init:jdroptarget component:jcomponent control:controlObj];
JNI_COCOA_EXIT(env);

    return ptr_to_jlong(dropTarget);
}

/*
 * Class:     sun_lwawt_macosx_CDropTarget
 * Method:    releaseNativeDropTarget
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CDropTarget_releaseNativeDropTarget
  (JNIEnv *env, jobject jthis, jlong nativeDropTargetVal)
{
    id dropTarget = (id)jlong_to_ptr(nativeDropTargetVal);

JNI_COCOA_ENTER(env);
    [dropTarget removeFromView:env];
JNI_COCOA_EXIT(env);
}
