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#import "ThreadUtilities.h"
28#import "jni_util.h" 
29#import <Cocoa/Cocoa.h>
30#import <JavaNativeFoundation/JavaNativeFoundation.h>
31
32@interface CClipboard : NSObject { }
33@property NSInteger changeCount;
34@property jobject clipboardOwner;
35
36+ (CClipboard*)sharedClipboard;
37- (void)declareTypes:(NSArray *)types withOwner:(jobject)owner jniEnv:(JNIEnv*)env;
38- (void)checkPasteboard:(id)sender;
39@end
40
41@implementation CClipboard
42@synthesize changeCount = _changeCount;
43@synthesize clipboardOwner = _clipboardOwner;
44
45// Clipboard creation is synchronized at the Java level
46+ (CClipboard*)sharedClipboard {
47    static CClipboard* sClipboard = nil;
48    if (sClipboard == nil) {
49        sClipboard = [[CClipboard alloc] init];
50        [[NSNotificationCenter defaultCenter] addObserver:sClipboard selector: @selector(checkPasteboard:)
51                                                     name: NSApplicationDidBecomeActiveNotification
52                                                   object: nil];
53    }
54
55    return sClipboard;
56}
57
58- (id)init {
59    if (self = [super init]) {
60        self.changeCount = [[NSPasteboard generalPasteboard] changeCount];
61    }
62    return self;
63}
64
65- (void)declareTypes:(NSArray*)types withOwner:(jobject)owner jniEnv:(JNIEnv*)env {
66    @synchronized(self) {
67        if (owner != NULL) {
68            if (self.clipboardOwner != NULL) {
69                JNFDeleteGlobalRef(env, self.clipboardOwner);
70            }
71            self.clipboardOwner = JNFNewGlobalRef(env, owner);
72        }
73    }
74    [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
75        self.changeCount = [[NSPasteboard generalPasteboard] declareTypes:types owner:self];
76    }];
77}
78
79- (void)checkPasteboard:(id)sender {
80
81    // This is called via NSApplicationDidBecomeActiveNotification.
82    
83    // If the change count on the general pasteboard is different than when we set it
84    // someone else put data on the clipboard.  That means the current owner lost ownership.
85    
86    NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
87
88    if (self.changeCount != newChangeCount) {
89        self.changeCount = newChangeCount;
90
91        // Notify that the content might be changed
92        static JNF_CLASS_CACHE(jc_CClipboard, "sun/lwawt/macosx/CClipboard");
93        static JNF_STATIC_MEMBER_CACHE(jm_contentChanged, jc_CClipboard, "notifyChanged", "()V");
94        JNIEnv *env = [ThreadUtilities getJNIEnv];
95        JNFCallStaticVoidMethod(env, jm_contentChanged);
96
97        // If we have a Java pasteboard owner, tell it that it doesn't own the pasteboard anymore.
98        static JNF_MEMBER_CACHE(jm_lostOwnership, jc_CClipboard, "notifyLostOwnership", "()V");
99        @synchronized(self) {
100            if (self.clipboardOwner) {
101                JNIEnv *env = [ThreadUtilities getJNIEnv];
102                JNFCallVoidMethod(env, self.clipboardOwner, jm_lostOwnership); // AWT_THREADING Safe (event)
103                JNFDeleteGlobalRef(env, self.clipboardOwner);
104                self.clipboardOwner = NULL;
105            }
106        }
107    }
108}
109
110- (BOOL) checkPasteboardWithoutNotification:(id)application {
111    AWT_ASSERT_APPKIT_THREAD;
112    
113    NSInteger newChangeCount = [[NSPasteboard generalPasteboard] changeCount];
114    
115    if (self.changeCount != newChangeCount) {
116        self.changeCount = newChangeCount;    
117        return YES;
118    } else {
119        return NO;
120    }
121}
122
123@end
124
125/*
126 * Class:     sun_lwawt_macosx_CClipboard
127 * Method:    declareTypes
128 * Signature: ([JLsun/awt/datatransfer/SunClipboard;)V
129*/
130JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_declareTypes
131(JNIEnv *env, jobject inObject, jlongArray inTypes, jobject inJavaClip)
132{
133JNF_COCOA_ENTER(env);
134
135    jint i;
136    jint nElements = (*env)->GetArrayLength(env, inTypes);
137    NSMutableArray *formatArray = [NSMutableArray arrayWithCapacity:nElements];
138    jlong *elements = (*env)->GetPrimitiveArrayCritical(env, inTypes, NULL);
139
140    for (i = 0; i < nElements; i++) {
141        NSString *pbFormat = formatForIndex(elements[i]);
142        if (pbFormat)
143            [formatArray addObject:pbFormat];
144    }
145
146    (*env)->ReleasePrimitiveArrayCritical(env, inTypes, elements, JNI_ABORT);
147    [[CClipboard sharedClipboard] declareTypes:formatArray withOwner:inJavaClip jniEnv:env];
148JNF_COCOA_EXIT(env);
149}
150
151/*
152 * Class:     sun_lwawt_macosx_CClipboard
153 * Method:    setData
154 * Signature: ([BJ)V
155*/
156JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CClipboard_setData
157(JNIEnv *env, jobject inObject, jbyteArray inBytes, jlong inFormat)
158{
159    if (inBytes == NULL) {
160        return;
161    }
162
163JNF_COCOA_ENTER(env);
164    jint nBytes = (*env)->GetArrayLength(env, inBytes);
165    jbyte *rawBytes = (*env)->GetPrimitiveArrayCritical(env, inBytes, NULL);
166    CHECK_NULL(rawBytes);
167    NSData *bytesAsData = [NSData dataWithBytes:rawBytes length:nBytes];
168    (*env)->ReleasePrimitiveArrayCritical(env, inBytes, rawBytes, JNI_ABORT);
169    NSString *format = formatForIndex(inFormat);
170    [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
171        [[NSPasteboard generalPasteboard] setData:bytesAsData forType:format];
172    }];
173JNF_COCOA_EXIT(env);
174}
175
176/*
177 * Class:     sun_lwawt_macosx_CClipboard
178 * Method:    getClipboardFormats
179 * Signature: (J)[J
180     */
181JNIEXPORT jlongArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardFormats
182(JNIEnv *env, jobject inObject)
183{
184    jlongArray returnValue = NULL;
185JNF_COCOA_ENTER(env);
186
187    __block NSArray* dataTypes;
188    [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
189        dataTypes = [[[NSPasteboard generalPasteboard] types] retain];
190    }];
191    [dataTypes autorelease];
192    
193    NSUInteger nFormats = [dataTypes count];
194    NSUInteger knownFormats = 0;
195    NSUInteger i;
196
197    // There can be any number of formats on the general pasteboard.  Find out which ones
198    // we know about (i.e., live in the flavormap.properties).
199    for (i = 0; i < nFormats; i++) {
200        NSString *format = (NSString *)[dataTypes objectAtIndex:i];
201        if (indexForFormat(format) != -1)
202            knownFormats++;
203    }
204
205    returnValue = (*env)->NewLongArray(env, knownFormats);
206    if (returnValue == NULL) {
207        return NULL;
208    }
209
210    if (knownFormats == 0) {
211        return returnValue;
212    }
213
214    // Now go back and map the formats we found back to Java indexes.
215    jboolean isCopy;
216    jlong *lFormats = (*env)->GetLongArrayElements(env, returnValue, &isCopy);
217    jlong *saveFormats = lFormats;
218
219    for (i = 0; i < nFormats; i++) {
220        NSString *format = (NSString *)[dataTypes objectAtIndex:i];
221        jlong index = indexForFormat(format);
222
223        if (index != -1) {
224            *lFormats = index;
225            lFormats++;
226        }
227    }
228
229    (*env)->ReleaseLongArrayElements(env, returnValue, saveFormats, JNI_COMMIT);
230JNF_COCOA_EXIT(env);
231    return returnValue;
232}
233
234/*
235 * Class:     sun_lwawt_macosx_CClipboard
236 * Method:    getClipboardData
237 * Signature: (JJ)[B
238     */
239JNIEXPORT jbyteArray JNICALL Java_sun_lwawt_macosx_CClipboard_getClipboardData
240(JNIEnv *env, jobject inObject, jlong format)
241{
242    jbyteArray returnValue = NULL;
243
244    // Note that this routine makes no attempt to interpret the data, since we're returning
245    // a byte array back to Java.  CDataTransferer will do that if necessary.
246JNF_COCOA_ENTER(env);
247
248    NSString *formatAsString = formatForIndex(format);
249    __block NSData* clipData;
250    [ThreadUtilities performOnMainThreadWaiting:YES block:^() {
251        clipData = [[[NSPasteboard generalPasteboard] dataForType:formatAsString] retain];
252    }];
253    
254    if (clipData == NULL) {
255        [JNFException raise:env as:"java/io/IOException" reason:"Font transform has NaN position"];
256        return NULL;
257    } else {
258        [clipData autorelease];
259    }
260
261    NSUInteger dataSize = [clipData length];
262    returnValue = (*env)->NewByteArray(env, dataSize);
263    if (returnValue == NULL) {
264        return NULL;
265    }
266
267    if (dataSize != 0) {
268        const void *dataBuffer = [clipData bytes];
269        (*env)->SetByteArrayRegion(env, returnValue, 0, dataSize, (jbyte *)dataBuffer);
270    }
271
272JNF_COCOA_EXIT(env);
273    return returnValue;
274}
275
276/*                                                                                            
277 * Class:     sun_lwawt_macosx_CClipboard                                                     
278 * Method:    checkPasteboard                                                                 
279 * Signature: ()V                                                                             
280 */                                                                                           
281JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CClipboard_checkPasteboardWithoutNotification
282(JNIEnv *env, jobject inObject)                                                               
283{                                                                                             
284    __block BOOL ret = NO;                                                                    
285    JNF_COCOA_ENTER(env);                                                                     
286    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){                                
287        ret = [[CClipboard sharedClipboard] checkPasteboardWithoutNotification:nil];          
288    }];                                                                                       
289                                                                                              
290    JNF_COCOA_EXIT(env);                                                                      
291    return ret;                                                                               
292}                                                                                             
293