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 }