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 }