1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #import "CDataTransferer.h"
  27 #include "sun_lwawt_macosx_CDataTransferer.h"
  28 
  29 #import <AppKit/AppKit.h>
  30 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  31 #import "jni_util.h"
  32 
  33 #include "ThreadUtilities.h"
  34 
  35 
  36 // ***** NOTE ***** This dictionary corresponds to the static array predefinedClipboardNames
  37 // in CDataTransferer.java.
  38 NSMutableDictionary *sStandardMappings = nil;
  39 
  40 NSMutableDictionary *getMappingTable() {
  41     if (sStandardMappings == nil) {
  42         sStandardMappings = [[NSMutableDictionary alloc] init];
  43         [sStandardMappings setObject:NSStringPboardType
  44                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_STRING]];
  45         [sStandardMappings setObject:NSFilenamesPboardType
  46                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_FILE]];
  47         [sStandardMappings setObject:NSTIFFPboardType
  48                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_TIFF]];
  49         [sStandardMappings setObject:NSRTFPboardType
  50                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_RICH_TEXT]];
  51         [sStandardMappings setObject:NSHTMLPboardType
  52                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_HTML]];
  53         [sStandardMappings setObject:NSPDFPboardType
  54                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_PDF]];
  55         [sStandardMappings setObject:NSURLPboardType
  56                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_URL]];
  57         [sStandardMappings setObject:NSPasteboardTypePNG
  58                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_PNG]];
  59         [sStandardMappings setObject:@"public.jpeg"
  60                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_JPEG]];
  61     }
  62     return sStandardMappings;
  63 }
  64 
  65 /*
  66  * Convert from a standard NSPasteboard data type to an index in our mapping table.
  67  */
  68 jlong indexForFormat(NSString *format) {
  69     jlong returnValue = -1;
  70 
  71     NSMutableDictionary *mappingTable = getMappingTable();
  72     NSArray *matchingKeys = [mappingTable allKeysForObject:format];
  73 
  74     // There should only be one matching key here...
  75     if ([matchingKeys count] > 0) {
  76         NSNumber *formatID = (NSNumber *)[matchingKeys objectAtIndex:0];
  77         returnValue = [formatID longValue];
  78     }
  79 
  80     // If we don't recognize the format, but it's a Java "custom" format register it
  81     if (returnValue == -1 && ([format hasPrefix:@"JAVA_DATAFLAVOR:"]) ) {
  82         returnValue = registerFormatWithPasteboard(format);
  83     }
  84 
  85     return returnValue;
  86 }
  87 
  88 /*
  89  * Inverse of above -- given a long int index, get the matching data format NSString.
  90  */
  91 NSString *formatForIndex(jlong inFormatCode) {
  92     return [getMappingTable() objectForKey:[NSNumber numberWithLong:inFormatCode]];
  93 }
  94 
  95 jlong registerFormatWithPasteboard(NSString *format) {
  96     NSMutableDictionary *mappingTable = getMappingTable();
  97     NSUInteger nextID = [mappingTable count] + 1;
  98     [mappingTable setObject:format forKey:[NSNumber numberWithLong:nextID]];
  99     return nextID;
 100 }
 101 
 102 
 103 /*
 104  * Class:     sun_lwawt_macosx_CDataTransferer
 105  * Method:    registerFormatWithPasteboard
 106  * Signature: (Ljava/lang/String;)J
 107  */
 108 JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CDataTransferer_registerFormatWithPasteboard
 109 (JNIEnv *env, jobject jthis, jstring newformat)
 110 {
 111     jlong returnValue = -1;
 112 JNF_COCOA_ENTER(env);
 113     returnValue = registerFormatWithPasteboard(JNFJavaToNSString(env, newformat));
 114 JNF_COCOA_EXIT(env);
 115     return returnValue;
 116 }
 117 
 118 /*
 119  * Class:     sun_lwawt_macosx_CDataTransferer
 120  * Method:    formatForIndex
 121  * Signature: (J)Ljava/lang/String;
 122  */
 123 JNIEXPORT jstring JNICALL Java_sun_lwawt_macosx_CDataTransferer_formatForIndex
 124   (JNIEnv *env, jobject jthis, jlong index)
 125 {
 126     jstring returnValue = NULL;
 127 JNF_COCOA_ENTER(env);
 128     returnValue = JNFNSToJavaString(env, formatForIndex(index));
 129 JNF_COCOA_EXIT(env);
 130     return returnValue;
 131 }
 132 
 133 /*
 134  * Class:     sun_lwawt_macosx_CDataTransferer
 135  * Method:    imageDataToPlatformImageBytes
 136  * Signature: ([III)[B
 137      */
 138 JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CDataTransferer_imageDataToPlatformImageBytes
 139 (JNIEnv *env, jobject obj, jintArray inPixelData, jint inWidth, jint inHeight)
 140 {
 141     jbyteArray returnValue = nil;
 142 JNF_COCOA_ENTER(env);
 143     UInt32 *rawImageData = (UInt32 *)(*env)->GetPrimitiveArrayCritical(env, inPixelData, 0);
 144 
 145     // The pixel data is in premultiplied ARGB format. That's exactly what
 146     // we need for the bitmap image rep.
 147     if (rawImageData != NULL) {
 148         NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
 149                                                                              pixelsWide:inWidth
 150                                                                              pixelsHigh:inHeight
 151                                                                           bitsPerSample:8
 152                                                                         samplesPerPixel:4
 153                                                                                hasAlpha:YES
 154                                                                                isPlanar:NO
 155                                                                          colorSpaceName:NSDeviceRGBColorSpace
 156                                                                             bytesPerRow:(inWidth*4)
 157                                                                            bitsPerPixel:32];
 158 
 159         // Conver the ARGB data into RGBA data that the bitmap can draw.
 160         unsigned char *destData = [imageRep bitmapData];
 161         unsigned char *currentRowBase;
 162         jint x, y;
 163 
 164         for (y = 0; y < inHeight; y++) {
 165             currentRowBase = destData + y * (inWidth * 4);
 166             unsigned char *currElement = currentRowBase;
 167             for (x = 0; x < inWidth; x++) {
 168                 UInt32 currPixel = rawImageData[y * inWidth + x];
 169                 *currElement++ = ((currPixel & 0xFF0000) >> 16);
 170                 *currElement++ = ((currPixel & 0xFF00) >> 8);
 171                 *currElement++ = (currPixel & 0xFF);
 172                 *currElement++ = ((currPixel & 0xFF000000) >> 24);
 173             }
 174         }
 175 
 176         (*env)->ReleasePrimitiveArrayCritical(env, inPixelData, rawImageData, JNI_ABORT);
 177         NSData *tiffImage = [imageRep TIFFRepresentation];
 178         jsize tiffSize = (jsize)[tiffImage length]; // #warning 64-bit: -length returns NSUInteger, but NewByteArray takes jsize
 179         returnValue = (*env)->NewByteArray(env, tiffSize);
 180         CHECK_NULL_RETURN(returnValue, nil);
 181         jbyte *tiffData = (jbyte *)(*env)->GetPrimitiveArrayCritical(env, returnValue, 0);
 182         CHECK_NULL_RETURN(tiffData, nil);
 183         [tiffImage getBytes:tiffData];
 184         (*env)->ReleasePrimitiveArrayCritical(env, returnValue, tiffData, 0); // Do not use JNI_COMMIT, as that will not free the buffer copy when +ProtectJavaHeap is on.
 185         [imageRep release];
 186     }
 187 JNF_COCOA_EXIT(env);
 188     return returnValue;
 189 
 190 }
 191 
 192 static jobject getImageForByteStream(JNIEnv *env, jbyteArray sourceData)
 193 {
 194     CHECK_NULL_RETURN(sourceData, NULL);
 195 
 196     jsize sourceSize = (*env)->GetArrayLength(env, sourceData);
 197     if (sourceSize == 0) return NULL;
 198 
 199     jbyte *sourceBytes = (*env)->GetPrimitiveArrayCritical(env, sourceData, NULL);
 200     CHECK_NULL_RETURN(sourceBytes, NULL);
 201     NSData *rawData = [NSData dataWithBytes:sourceBytes length:sourceSize];
 202 
 203     NSImage *newImage = [[NSImage alloc] initWithData:rawData];
 204     if (newImage) CFRetain(newImage); // GC
 205     [newImage release];
 206 
 207     (*env)->ReleasePrimitiveArrayCritical(env, sourceData, sourceBytes, JNI_ABORT);
 208     CHECK_NULL_RETURN(newImage, NULL);
 209 
 210     // The ownership of the NSImage is passed to the new CImage jobject. No need to release it.
 211     static JNF_CLASS_CACHE(jc_CImage, "sun/lwawt/macosx/CImage");
 212     static JNF_STATIC_MEMBER_CACHE(jm_CImage_getCreator, jc_CImage, "getCreator", "()Lsun/lwawt/macosx/CImage$Creator;");
 213     jobject creator = JNFCallStaticObjectMethod(env, jm_CImage_getCreator);
 214 
 215     static JNF_CLASS_CACHE(jc_CImage_Generator, "sun/lwawt/macosx/CImage$Creator");
 216     static JNF_MEMBER_CACHE(jm_CImage_Generator_createImageUsingNativeSize, jc_CImage_Generator, "createImageUsingNativeSize", "(J)Ljava/awt/image/BufferedImage;");
 217     return JNFCallObjectMethod(env, creator, jm_CImage_Generator_createImageUsingNativeSize, ptr_to_jlong(newImage)); // AWT_THREADING Safe (known object)
 218 }
 219 
 220 /*
 221  * Class:     sun_lwawt_macosx_CDataTransferer
 222  * Method:    getImageForByteStream
 223  * Signature: ([B)Ljava/awt/Image;
 224  */
 225 JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CDataTransferer_getImageForByteStream
 226   (JNIEnv *env, jobject obj, jbyteArray sourceData)
 227 {
 228     jobject img = NULL;
 229 JNF_COCOA_ENTER(env);
 230     img = getImageForByteStream(env, sourceData);
 231 JNF_COCOA_EXIT(env);
 232     return img;
 233 }
 234 
 235 static jobjectArray CreateJavaFilenameArray(JNIEnv *env, NSArray *filenameArray)
 236 {
 237     NSUInteger filenameCount = [filenameArray count];
 238     if (filenameCount == 0) return nil;
 239 
 240     // Get the java.lang.String class object:
 241     jclass stringClazz = (*env)->FindClass(env, "java/lang/String");
 242     CHECK_NULL_RETURN(stringClazz, nil);
 243     jobject jfilenameArray = (*env)->NewObjectArray(env, filenameCount, stringClazz, NULL); // AWT_THREADING Safe (known object)
 244     if ((*env)->ExceptionOccurred(env)) {
 245         (*env)->ExceptionDescribe(env);
 246         (*env)->ExceptionClear(env);
 247         return nil;
 248     }
 249     if (!jfilenameArray) {
 250         NSLog(@"CDataTransferer_CreateJavaFilenameArray: couldn't create jfilenameArray.");
 251         return nil;
 252     }
 253     (*env)->DeleteLocalRef(env, stringClazz);
 254 
 255     // Iterate through all the filenames:
 256     NSUInteger i;
 257     for (i = 0; i < filenameCount; i++) {
 258         NSMutableString *stringVal = [[NSMutableString alloc] initWithString:[filenameArray objectAtIndex:i]];
 259         CFStringNormalize((CFMutableStringRef)stringVal, kCFStringNormalizationFormC);
 260         const char* stringBytes = [stringVal UTF8String];
 261 
 262         // Create a Java String:
 263         jstring string = (*env)->NewStringUTF(env, stringBytes);
 264         if ((*env)->ExceptionOccurred(env)) {
 265             (*env)->ExceptionDescribe(env);
 266             (*env)->ExceptionClear(env);
 267             continue;
 268         }
 269         if (!string) {
 270             NSLog(@"CDataTransferer_CreateJavaFilenameArray: couldn't create jstring[%lu] for [%@].", (unsigned long) i, stringVal);
 271             continue;
 272         }
 273 
 274         // Set the Java array element with our String:
 275         (*env)->SetObjectArrayElement(env, jfilenameArray, i, string);
 276         if ((*env)->ExceptionOccurred(env)) {
 277             (*env)->ExceptionDescribe(env);
 278             (*env)->ExceptionClear(env);
 279             continue;
 280         }
 281 
 282         // Release local String reference:
 283         (*env)->DeleteLocalRef(env, string);
 284     }
 285 
 286     return jfilenameArray;
 287 }
 288 
 289 /*
 290  * Class:     sun_lwawt_macosx_CDataTransferer
 291  * Method:    draqQueryFile
 292  * Signature: ([B)[Ljava/lang/String;
 293  */
 294 JNIEXPORT jobjectArray JNICALL
 295 Java_sun_lwawt_macosx_CDataTransferer_nativeDragQueryFile
 296 (JNIEnv *env, jclass clazz, jbyteArray jbytearray)
 297 {
 298     // Decodes a byte array into a set of String filenames.
 299     // bytes here is an XML property list containing all of the filenames in the drag.
 300     // Parse the XML list into strings and return an array of Java strings matching all of the
 301     // files in the list.
 302 
 303     jobjectArray jreturnArray = NULL;
 304 
 305 JNF_COCOA_ENTER(env);
 306     // Get byte array elements:
 307     jboolean isCopy;
 308     jbyte* jbytes = (*env)->GetByteArrayElements(env, jbytearray, &isCopy);
 309     if (jbytes == NULL) {
 310         return NULL;
 311     }
 312 
 313     // Wrap jbytes in an NSData object:
 314     jsize jbytesLength = (*env)->GetArrayLength(env, jbytearray);
 315     NSData *xmlData = [NSData dataWithBytesNoCopy:jbytes length:jbytesLength freeWhenDone:NO];
 316 
 317     // Create a property list from the Java data:
 318     NSString *errString = nil;
 319     NSPropertyListFormat plistFormat = 0;
 320     id plist = [NSPropertyListSerialization propertyListFromData:xmlData mutabilityOption:NSPropertyListImmutable
 321         format:&plistFormat errorDescription:&errString];
 322 
 323     // The property list must be an array of strings:
 324     if (plist == nil || [plist isKindOfClass:[NSArray class]] == FALSE) {
 325         NSLog(@"CDataTransferer_dragQueryFile: plist not a valid NSArray (error %@):\n%@", errString, plist);
 326         (*env)->ReleaseByteArrayElements(env, jbytearray, jbytes, JNI_ABORT);
 327         return NULL;
 328     }
 329 
 330     // Transfer all string items from the plistArray to filenameArray. This wouldn't be necessary
 331     // if we could trust the array to contain all valid elements but this way we'll be sure.
 332     NSArray *plistArray = (NSArray *)plist;
 333     NSUInteger plistItemCount = [plistArray count];
 334     NSMutableArray *filenameArray = [[NSMutableArray alloc] initWithCapacity:plistItemCount];
 335 
 336     NSUInteger i;
 337     for (i = 0; i < plistItemCount; i++) {
 338         // Filenames must be strings:
 339         id idVal = [plistArray objectAtIndex:i];
 340         if ([idVal isKindOfClass:[NSString class]] == FALSE) {
 341             NSLog(@"CDataTransferer_dragQueryFile: plist[%lu] not an NSString:\n%@", (unsigned long) i, idVal);
 342             continue;
 343         }
 344 
 345         [filenameArray addObject:idVal];
 346     }
 347 
 348     // Convert our array of filenames into a Java array of String filenames:
 349     jreturnArray = CreateJavaFilenameArray(env, filenameArray);
 350 
 351     [filenameArray release];
 352 
 353     // We're done with the jbytes (backing the plist/plistArray):
 354     (*env)->ReleaseByteArrayElements(env, jbytearray, jbytes, JNI_ABORT);
 355 JNF_COCOA_EXIT(env);
 356     return jreturnArray;
 357 }