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