1 /*
   2  * Copyright (c) 2011, 2015, 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 "JNIUtilities.h"
  30 
  31 // ***** NOTE ***** This dictionary corresponds to the static array predefinedClipboardNames
  32 // in CDataTransferer.java.
  33 NSMutableDictionary *sStandardMappings = nil;
  34 
  35 NSMutableDictionary *getMappingTable() {
  36     if (sStandardMappings == nil) {
  37         sStandardMappings = [[NSMutableDictionary alloc] init];
  38         [sStandardMappings setObject:NSStringPboardType
  39                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_STRING]];
  40         [sStandardMappings setObject:NSFilenamesPboardType
  41                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_FILE]];
  42         [sStandardMappings setObject:NSTIFFPboardType
  43                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_TIFF]];
  44         [sStandardMappings setObject:NSRTFPboardType
  45                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_RICH_TEXT]];
  46         [sStandardMappings setObject:NSHTMLPboardType
  47                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_HTML]];
  48         [sStandardMappings setObject:NSPDFPboardType
  49                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_PDF]];
  50         [sStandardMappings setObject:NSURLPboardType
  51                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_URL]];
  52         [sStandardMappings setObject:NSPasteboardTypePNG
  53                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_PNG]];
  54         [sStandardMappings setObject:(NSString*)kUTTypeJPEG
  55                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_JPEG]];
  56         [sStandardMappings setObject:NSPICTPboardType
  57                               forKey:[NSNumber numberWithLong:sun_lwawt_macosx_CDataTransferer_CF_XPICT]];
  58     }
  59     return sStandardMappings;
  60 }
  61 
  62 /*
  63  * Convert from a standard NSPasteboard data type to an index in our mapping table.
  64  */
  65 jlong indexForFormat(NSString *format) {
  66     jlong returnValue = -1;
  67 
  68     NSMutableDictionary *mappingTable = getMappingTable();
  69     NSArray *matchingKeys = [mappingTable allKeysForObject:format];
  70 
  71     // There should only be one matching key here...
  72     if ([matchingKeys count] > 0) {
  73         NSNumber *formatID = (NSNumber *)[matchingKeys objectAtIndex:0];
  74         returnValue = [formatID longValue];
  75     }
  76 
  77     // If we don't recognize the format, but it's a Java "custom" format register it
  78     if (returnValue == -1 && ([format hasPrefix:@"JAVA_DATAFLAVOR:"]) ) {
  79         returnValue = registerFormatWithPasteboard(format);
  80     }
  81 
  82     return returnValue;
  83 }
  84 
  85 /*
  86  * Inverse of above -- given a long int index, get the matching data format NSString.
  87  */
  88 NSString *formatForIndex(jlong inFormatCode) {
  89     return [getMappingTable() objectForKey:[NSNumber numberWithLong:inFormatCode]];
  90 }
  91 
  92 jlong registerFormatWithPasteboard(NSString *format) {
  93     NSMutableDictionary *mappingTable = getMappingTable();
  94     NSUInteger nextID = [mappingTable count] + 1;
  95     [mappingTable setObject:format forKey:[NSNumber numberWithLong:nextID]];
  96     return nextID;
  97 }
  98 
  99 
 100 /*
 101  * Class:     sun_lwawt_macosx_CDataTransferer
 102  * Method:    registerFormatWithPasteboard
 103  * Signature: (Ljava/lang/String;)J
 104  */
 105 JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CDataTransferer_registerFormatWithPasteboard
 106 (JNIEnv *env, jobject jthis, jstring newformat)
 107 {
 108     jlong returnValue = -1;
 109 JNI_COCOA_ENTER(env);
 110     returnValue = registerFormatWithPasteboard(JavaStringToNSString(env, newformat));
 111 JNI_COCOA_EXIT(env);
 112     return returnValue;
 113 }
 114 
 115 /*
 116  * Class:     sun_lwawt_macosx_CDataTransferer
 117  * Method:    formatForIndex
 118  * Signature: (J)Ljava/lang/String;
 119  */
 120 JNIEXPORT jstring JNICALL Java_sun_lwawt_macosx_CDataTransferer_formatForIndex
 121   (JNIEnv *env, jobject jthis, jlong index)
 122 {
 123     jstring returnValue = NULL;
 124 JNI_COCOA_ENTER(env);
 125     returnValue = NSStringToJavaString(env, formatForIndex(index));
 126 JNI_COCOA_EXIT(env);
 127     return returnValue;
 128 }
 129 
 130 static jobjectArray CreateJavaFilenameArray(JNIEnv *env, NSArray *filenameArray)
 131 {
 132     NSUInteger filenameCount = [filenameArray count];
 133     if (filenameCount == 0) return nil;
 134 
 135     // Get the java.lang.String class object:
 136     jclass stringClazz = (*env)->FindClass(env, "java/lang/String");
 137     CHECK_NULL_RETURN(stringClazz, nil);
 138     jobject jfilenameArray = (*env)->NewObjectArray(env, filenameCount, stringClazz, NULL);
 139     if ((*env)->ExceptionOccurred(env)) {
 140         (*env)->ExceptionDescribe(env);
 141         (*env)->ExceptionClear(env);
 142         return nil;
 143     }
 144     if (!jfilenameArray) {
 145         NSLog(@"CDataTransferer_CreateJavaFilenameArray: couldn't create jfilenameArray.");
 146         return nil;
 147     }
 148     (*env)->DeleteLocalRef(env, stringClazz);
 149 
 150     // Iterate through all the filenames:
 151     NSUInteger i;
 152     for (i = 0; i < filenameCount; i++) {
 153         NSMutableString *stringVal = [[NSMutableString alloc] initWithString:[filenameArray objectAtIndex:i]];
 154         CFStringNormalize((CFMutableStringRef)stringVal, kCFStringNormalizationFormC);
 155         const char* stringBytes = [stringVal UTF8String];
 156 
 157         // Create a Java String:
 158         jstring string = (*env)->NewStringUTF(env, stringBytes);
 159         if ((*env)->ExceptionOccurred(env)) {
 160             (*env)->ExceptionDescribe(env);
 161             (*env)->ExceptionClear(env);
 162             continue;
 163         }
 164         if (!string) {
 165             NSLog(@"CDataTransferer_CreateJavaFilenameArray: couldn't create jstring[%lu] for [%@].", (unsigned long) i, stringVal);
 166             continue;
 167         }
 168 
 169         // Set the Java array element with our String:
 170         (*env)->SetObjectArrayElement(env, jfilenameArray, i, string);
 171         if ((*env)->ExceptionOccurred(env)) {
 172             (*env)->ExceptionDescribe(env);
 173             (*env)->ExceptionClear(env);
 174             continue;
 175         }
 176 
 177         // Release local String reference:
 178         (*env)->DeleteLocalRef(env, string);
 179     }
 180 
 181     return jfilenameArray;
 182 }
 183 
 184 /*
 185  * Class:     sun_lwawt_macosx_CDataTransferer
 186  * Method:    draqQueryFile
 187  * Signature: ([B)[Ljava/lang/String;
 188  */
 189 JNIEXPORT jobjectArray JNICALL
 190 Java_sun_lwawt_macosx_CDataTransferer_nativeDragQueryFile
 191 (JNIEnv *env, jclass clazz, jbyteArray jbytearray)
 192 {
 193     // Decodes a byte array into a set of String filenames.
 194     // bytes here is an XML property list containing all of the filenames in the drag.
 195     // Parse the XML list into strings and return an array of Java strings matching all of the
 196     // files in the list.
 197 
 198     jobjectArray jreturnArray = NULL;
 199 
 200 JNI_COCOA_ENTER(env);
 201     // Get byte array elements:
 202     jboolean isCopy;
 203     jbyte* jbytes = (*env)->GetByteArrayElements(env, jbytearray, &isCopy);
 204     if (jbytes == NULL) {
 205         return NULL;
 206     }
 207 
 208     // Wrap jbytes in an NSData object:
 209     jsize jbytesLength = (*env)->GetArrayLength(env, jbytearray);
 210     NSData *xmlData = [NSData dataWithBytesNoCopy:jbytes length:jbytesLength freeWhenDone:NO];
 211 
 212     // Create a property list from the Java data:
 213     NSString *errString = nil;
 214     NSPropertyListFormat plistFormat = 0;
 215     id plist = [NSPropertyListSerialization propertyListFromData:xmlData mutabilityOption:NSPropertyListImmutable
 216         format:&plistFormat errorDescription:&errString];
 217 
 218     // The property list must be an array of strings:
 219     if (plist == nil || [plist isKindOfClass:[NSArray class]] == FALSE) {
 220         NSLog(@"CDataTransferer_dragQueryFile: plist not a valid NSArray (error %@):\n%@", errString, plist);
 221         (*env)->ReleaseByteArrayElements(env, jbytearray, jbytes, JNI_ABORT);
 222         return NULL;
 223     }
 224 
 225     // Transfer all string items from the plistArray to filenameArray. This wouldn't be necessary
 226     // if we could trust the array to contain all valid elements but this way we'll be sure.
 227     NSArray *plistArray = (NSArray *)plist;
 228     NSUInteger plistItemCount = [plistArray count];
 229     NSMutableArray *filenameArray = [[NSMutableArray alloc] initWithCapacity:plistItemCount];
 230 
 231     NSUInteger i;
 232     for (i = 0; i < plistItemCount; i++) {
 233         // Filenames must be strings:
 234         id idVal = [plistArray objectAtIndex:i];
 235         if ([idVal isKindOfClass:[NSString class]] == FALSE) {
 236             NSLog(@"CDataTransferer_dragQueryFile: plist[%lu] not an NSString:\n%@", (unsigned long) i, idVal);
 237             continue;
 238         }
 239 
 240         [filenameArray addObject:idVal];
 241     }
 242 
 243     // Convert our array of filenames into a Java array of String filenames:
 244     jreturnArray = CreateJavaFilenameArray(env, filenameArray);
 245 
 246     [filenameArray release];
 247 
 248     // We're done with the jbytes (backing the plist/plistArray):
 249     (*env)->ReleaseByteArrayElements(env, jbytearray, jbytes, JNI_ABORT);
 250 JNI_COCOA_EXIT(env);
 251     return jreturnArray;
 252 }