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     }
  58     return sStandardMappings;
  59 }
  60 
  61 /*
  62  * Convert from a standard NSPasteboard data type to an index in our mapping table.
  63  */
  64 jlong indexForFormat(NSString *format) {
  65     jlong returnValue = -1;
  66 
  67     NSMutableDictionary *mappingTable = getMappingTable();
  68     NSArray *matchingKeys = [mappingTable allKeysForObject:format];
  69 
  70     // There should only be one matching key here...
  71     if ([matchingKeys count] > 0) {
  72         NSNumber *formatID = (NSNumber *)[matchingKeys objectAtIndex:0];
  73         returnValue = [formatID longValue];
  74     }
  75 
  76     // If we don't recognize the format, but it's a Java "custom" format register it
  77     if (returnValue == -1 && ([format hasPrefix:@"JAVA_DATAFLAVOR:"]) ) {
  78         returnValue = registerFormatWithPasteboard(format);
  79     }
  80 
  81     return returnValue;
  82 }
  83 
  84 /*
  85  * Inverse of above -- given a long int index, get the matching data format NSString.
  86  */
  87 NSString *formatForIndex(jlong inFormatCode) {
  88     return [getMappingTable() objectForKey:[NSNumber numberWithLong:inFormatCode]];
  89 }
  90 
  91 jlong registerFormatWithPasteboard(NSString *format) {
  92     NSMutableDictionary *mappingTable = getMappingTable();
  93     NSUInteger nextID = [mappingTable count] + 1;
  94     [mappingTable setObject:format forKey:[NSNumber numberWithLong:nextID]];
  95     return nextID;
  96 }
  97 
  98 
  99 /*
 100  * Class:     sun_lwawt_macosx_CDataTransferer
 101  * Method:    registerFormatWithPasteboard
 102  * Signature: (Ljava/lang/String;)J
 103  */
 104 JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CDataTransferer_registerFormatWithPasteboard
 105 (JNIEnv *env, jobject jthis, jstring newformat)
 106 {
 107     jlong returnValue = -1;
 108 JNF_COCOA_ENTER(env);
 109     returnValue = registerFormatWithPasteboard(JNFJavaToNSString(env, newformat));
 110 JNF_COCOA_EXIT(env);
 111     return returnValue;
 112 }
 113 
 114 /*
 115  * Class:     sun_lwawt_macosx_CDataTransferer
 116  * Method:    formatForIndex
 117  * Signature: (J)Ljava/lang/String;
 118  */
 119 JNIEXPORT jstring JNICALL Java_sun_lwawt_macosx_CDataTransferer_formatForIndex
 120   (JNIEnv *env, jobject jthis, jlong index)
 121 {
 122     jstring returnValue = NULL;
 123 JNF_COCOA_ENTER(env);
 124     returnValue = JNFNSToJavaString(env, formatForIndex(index));
 125 JNF_COCOA_EXIT(env);
 126     return returnValue;
 127 }
 128 
 129 /*
 130  * Class:     sun_lwawt_macosx_CDataTransferer
 131  * Method:    imageDataToPlatformImageBytes
 132  * Signature: ([III)[B
 133      */
 134 JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CDataTransferer_imageDataToPlatformImageBytes
 135 (JNIEnv *env, jobject obj, jintArray inPixelData, jint inWidth, jint inHeight)
 136 {
 137     jbyteArray returnValue = nil;
 138 JNF_COCOA_ENTER(env);
 139     UInt32 *rawImageData = (UInt32 *)(*env)->GetPrimitiveArrayCritical(env, inPixelData, 0);
 140 
 141     // The pixel data is in premultiplied ARGB format. That's exactly what
 142     // we need for the bitmap image rep.
 143     if (rawImageData != NULL) {
 144         NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
 145                                                                              pixelsWide:inWidth
 146                                                                              pixelsHigh:inHeight
 147                                                                           bitsPerSample:8
 148                                                                         samplesPerPixel:4
 149                                                                                hasAlpha:YES
 150                                                                                isPlanar:NO
 151                                                                          colorSpaceName:NSCalibratedRGBColorSpace
 152                                                                             bytesPerRow:(inWidth*4)
 153                                                                            bitsPerPixel:32];
 154 
 155         // Conver the ARGB data into RGBA data that the bitmap can draw.
 156         unsigned char *destData = [imageRep bitmapData];
 157         unsigned char *currentRowBase;
 158         jint x, y;
 159 
 160         for (y = 0; y < inHeight; y++) {
 161             currentRowBase = destData + y * (inWidth * 4);
 162             unsigned char *currElement = currentRowBase;
 163             for (x = 0; x < inWidth; x++) {
 164                 UInt32 currPixel = rawImageData[y * inWidth + x];
 165                 *currElement++ = ((currPixel & 0xFF0000) >> 16);
 166                 *currElement++ = ((currPixel & 0xFF00) >> 8);
 167                 *currElement++ = (currPixel & 0xFF);
 168                 *currElement++ = ((currPixel & 0xFF000000) >> 24);
 169             }
 170         }
 171 
 172         (*env)->ReleasePrimitiveArrayCritical(env, inPixelData, rawImageData, JNI_ABORT);
 173         NSData *tiffImage = [imageRep TIFFRepresentation];
 174         jsize tiffSize = (jsize)[tiffImage length]; // #warning 64-bit: -length returns NSUInteger, but NewByteArray takes jsize
 175         returnValue = (*env)->NewByteArray(env, tiffSize);
 176         CHECK_NULL_RETURN(returnValue, nil);
 177         jbyte *tiffData = (jbyte *)(*env)->GetPrimitiveArrayCritical(env, returnValue, 0);
 178         CHECK_NULL_RETURN(tiffData, nil);
 179         [tiffImage getBytes:tiffData];
 180         (*env)->ReleasePrimitiveArrayCritical(env, returnValue, tiffData, 0); // Do not use JNI_COMMIT, as that will not free the buffer copy when +ProtectJavaHeap is on.
 181         [imageRep release];
 182     }
 183 JNF_COCOA_EXIT(env);
 184     return returnValue;
 185 
 186 }
 187 
 188 static jobject getImageForByteStream(JNIEnv *env, jbyteArray sourceData)
 189 {
 190     CHECK_NULL_RETURN(sourceData, NULL);
 191 
 192     jsize sourceSize = (*env)->GetArrayLength(env, sourceData);
 193     if (sourceSize == 0) return NULL;
 194 
 195     jbyte *sourceBytes = (*env)->GetPrimitiveArrayCritical(env, sourceData, NULL);
 196     CHECK_NULL_RETURN(sourceBytes, NULL);
 197     NSData *rawData = [NSData dataWithBytes:sourceBytes length:sourceSize];
 198 
 199     NSImage *newImage = [[NSImage alloc] initWithData:rawData];
 200     if (newImage) CFRetain(newImage); // GC
 201     [newImage release];
 202 
 203     (*env)->ReleasePrimitiveArrayCritical(env, sourceData, sourceBytes, JNI_ABORT);
 204     CHECK_NULL_RETURN(newImage, NULL);
 205 
 206     // The ownership of the NSImage is passed to the new CImage jobject. No need to release it.
 207     static JNF_CLASS_CACHE(jc_CImage, "sun/lwawt/macosx/CImage");
 208     static JNF_STATIC_MEMBER_CACHE(jm_CImage_getCreator, jc_CImage, "getCreator", "()Lsun/lwawt/macosx/CImage$Creator;");
 209     jobject creator = JNFCallStaticObjectMethod(env, jm_CImage_getCreator);
 210 
 211     static JNF_CLASS_CACHE(jc_CImage_Generator, "sun/lwawt/macosx/CImage$Creator");
 212     static JNF_MEMBER_CACHE(jm_CImage_Generator_createImageUsingNativeSize, jc_CImage_Generator, "createImageUsingNativeSize", "(J)Ljava/awt/image/BufferedImage;");
 213     return JNFCallObjectMethod(env, creator, jm_CImage_Generator_createImageUsingNativeSize, ptr_to_jlong(newImage)); // AWT_THREADING Safe (known object)
 214 }
 215 
 216 /*
 217  * Class:     sun_lwawt_macosx_CDataTransferer
 218  * Method:    getImageForByteStream
 219  * Signature: ([B)Ljava/awt/Image;
 220  */
 221 JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CDataTransferer_getImageForByteStream
 222   (JNIEnv *env, jobject obj, jbyteArray sourceData)
 223 {
 224     jobject img = NULL;
 225 JNF_COCOA_ENTER(env);
 226     img = getImageForByteStream(env, sourceData);
 227 JNF_COCOA_EXIT(env);
 228     return img;
 229 }
 230 
 231 static jobjectArray CreateJavaFilenameArray(JNIEnv *env, NSArray *filenameArray)
 232 {
 233     NSUInteger filenameCount = [filenameArray count];
 234     if (filenameCount == 0) return nil;
 235 
 236     // Get the java.lang.String class object:
 237     jclass stringClazz = (*env)->FindClass(env, "java/lang/String");
 238     CHECK_NULL_RETURN(stringClazz, nil);
 239     jobject jfilenameArray = (*env)->NewObjectArray(env, filenameCount, stringClazz, NULL); // AWT_THREADING Safe (known object)
 240     if ((*env)->ExceptionOccurred(env)) {
 241         (*env)->ExceptionDescribe(env);
 242         (*env)->ExceptionClear(env);
 243         return nil;
 244     }
 245     if (!jfilenameArray) {
 246         NSLog(@"CDataTransferer_CreateJavaFilenameArray: couldn't create jfilenameArray.");
 247         return nil;
 248     }
 249     (*env)->DeleteLocalRef(env, stringClazz);
 250 
 251     // Iterate through all the filenames:
 252     NSUInteger i;
 253     for (i = 0; i < filenameCount; i++) {
 254         NSMutableString *stringVal = [[NSMutableString alloc] initWithString:[filenameArray objectAtIndex:i]];
 255         CFStringNormalize((CFMutableStringRef)stringVal, kCFStringNormalizationFormC);
 256         const char* stringBytes = [stringVal UTF8String];
 257 
 258         // Create a Java String:
 259         jstring string = (*env)->NewStringUTF(env, stringBytes);
 260         if ((*env)->ExceptionOccurred(env)) {
 261             (*env)->ExceptionDescribe(env);
 262             (*env)->ExceptionClear(env);
 263             continue;
 264         }
 265         if (!string) {
 266             NSLog(@"CDataTransferer_CreateJavaFilenameArray: couldn't create jstring[%lu] for [%@].", (unsigned long) i, stringVal);
 267             continue;
 268         }
 269 
 270         // Set the Java array element with our String:
 271         (*env)->SetObjectArrayElement(env, jfilenameArray, i, string);
 272         if ((*env)->ExceptionOccurred(env)) {
 273             (*env)->ExceptionDescribe(env);
 274             (*env)->ExceptionClear(env);
 275             continue;
 276         }
 277 
 278         // Release local String reference:
 279         (*env)->DeleteLocalRef(env, string);
 280     }
 281 
 282     return jfilenameArray;
 283 }
 284 
 285 /*
 286  * Class:     sun_lwawt_macosx_CDataTransferer
 287  * Method:    draqQueryFile
 288  * Signature: ([B)[Ljava/lang/String;
 289  */
 290 JNIEXPORT jobjectArray JNICALL
 291 Java_sun_lwawt_macosx_CDataTransferer_nativeDragQueryFile
 292 (JNIEnv *env, jclass clazz, jbyteArray jbytearray)
 293 {
 294     // Decodes a byte array into a set of String filenames.
 295     // bytes here is an XML property list containing all of the filenames in the drag.
 296     // Parse the XML list into strings and return an array of Java strings matching all of the
 297     // files in the list.
 298 
 299     jobjectArray jreturnArray = NULL;
 300 
 301 JNF_COCOA_ENTER(env);
 302     // Get byte array elements:
 303     jboolean isCopy;
 304     jbyte* jbytes = (*env)->GetByteArrayElements(env, jbytearray, &isCopy);
 305     if (jbytes == NULL) {
 306         return NULL;
 307     }
 308 
 309     // Wrap jbytes in an NSData object:
 310     jsize jbytesLength = (*env)->GetArrayLength(env, jbytearray);
 311     NSData *xmlData = [NSData dataWithBytesNoCopy:jbytes length:jbytesLength freeWhenDone:NO];
 312 
 313     // Create a property list from the Java data:
 314     NSString *errString = nil;
 315     NSPropertyListFormat plistFormat = 0;
 316     id plist = [NSPropertyListSerialization propertyListFromData:xmlData mutabilityOption:NSPropertyListImmutable
 317         format:&plistFormat errorDescription:&errString];
 318 
 319     // The property list must be an array of strings:
 320     if (plist == nil || [plist isKindOfClass:[NSArray class]] == FALSE) {
 321         NSLog(@"CDataTransferer_dragQueryFile: plist not a valid NSArray (error %@):\n%@", errString, plist);
 322         (*env)->ReleaseByteArrayElements(env, jbytearray, jbytes, JNI_ABORT);
 323         return NULL;
 324     }
 325 
 326     // Transfer all string items from the plistArray to filenameArray. This wouldn't be necessary
 327     // if we could trust the array to contain all valid elements but this way we'll be sure.
 328     NSArray *plistArray = (NSArray *)plist;
 329     NSUInteger plistItemCount = [plistArray count];
 330     NSMutableArray *filenameArray = [[NSMutableArray alloc] initWithCapacity:plistItemCount];
 331 
 332     NSUInteger i;
 333     for (i = 0; i < plistItemCount; i++) {
 334         // Filenames must be strings:
 335         id idVal = [plistArray objectAtIndex:i];
 336         if ([idVal isKindOfClass:[NSString class]] == FALSE) {
 337             NSLog(@"CDataTransferer_dragQueryFile: plist[%lu] not an NSString:\n%@", (unsigned long) i, idVal);
 338             continue;
 339         }
 340 
 341         [filenameArray addObject:idVal];
 342     }
 343 
 344     // Convert our array of filenames into a Java array of String filenames:
 345     jreturnArray = CreateJavaFilenameArray(env, filenameArray);
 346 
 347     [filenameArray release];
 348 
 349     // We're done with the jbytes (backing the plist/plistArray):
 350     (*env)->ReleaseByteArrayElements(env, jbytearray, jbytes, JNI_ABORT);
 351 JNF_COCOA_EXIT(env);
 352     return jreturnArray;
 353 }