1 /*
2  * Copyright (c) 2011, 2017, 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 package apple.laf;
27 
28 import java.nio.*;
29 import java.util.*;
30 
31 import apple.laf.JRSUIConstants.*;
32 
33 public final class JRSUIControl {
initNativeJRSUI()34     private static native int initNativeJRSUI();
35 
getPtrOfBuffer(ByteBuffer byteBuffer)36     private static native long getPtrOfBuffer(ByteBuffer byteBuffer);
getCFDictionary(boolean flipped)37     private static native long getCFDictionary(boolean flipped);
disposeCFDictionary(long cfDictionaryPtr)38     private static native void disposeCFDictionary(long cfDictionaryPtr);
39 
syncChanges(long cfDictionaryPtr, long byteBufferPtr)40     private static native int syncChanges(long cfDictionaryPtr, long byteBufferPtr);
41 
42 //    private static native int paint(long cfDictionaryPtr, long oldProperties, long newProperties, OSXSurfaceData osxsd, double x, double y, double w, double h);
43 //    private static native int paintChanges(long cfDictionaryPtr, long byteBufferPtr, long oldProperties, long newProperties, OSXSurfaceData osxsd, double x, double y, double w, double h);
44 
paintToCGContext(long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h)45     private static native int paintToCGContext                    (long cgContext,    long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h);
paintChangesToCGContext(long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr)46     private static native int paintChangesToCGContext            (long cgContext,    long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr);
47 
paintImage(int[] data, int imgW, int imgH, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h)48     private static native int paintImage        (int[] data, int imgW, int imgH,    long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h);
paintChangesImage(int[] data, int imgW, int imgH, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr)49     private static native int paintChangesImage    (int[] data, int imgW, int imgH,    long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr);
50 
getNativeHitPart( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, double hitX, double hitY)51     private static native int getNativeHitPart(                            long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, double hitX, double hitY);
getNativePartBounds(final double[] rect, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int part)52     private static native void getNativePartBounds(final double[] rect,    long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int part);
getNativeScrollBarOffsetChange( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int offset, int visibleAmount, int extent)53     private static native double getNativeScrollBarOffsetChange(        long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int offset, int visibleAmount, int extent);
54 
55     private static final int INCOHERENT = 2;
56     private static final int NOT_INIT = 1;
57     private static final int SUCCESS = 0;
58     private static final int NULL_PTR = -1;
59     private static final int NULL_CG_REF = -2;
60 
61     private static int nativeJRSInitialized = NOT_INIT;
62 
63 
initJRSUI()64     public static void initJRSUI() {
65         if (nativeJRSInitialized == SUCCESS) return;
66         nativeJRSInitialized = initNativeJRSUI();
67         if (nativeJRSInitialized != SUCCESS) throw new RuntimeException("JRSUI could not be initialized (" + nativeJRSInitialized + ").");
68     }
69 
70     private static final int NIO_BUFFER_SIZE = 128;
71     private static class ThreadLocalByteBuffer {
72         final ByteBuffer buffer;
73         final long ptr;
74 
ThreadLocalByteBuffer()75         public ThreadLocalByteBuffer() {
76             buffer = ByteBuffer.allocateDirect(NIO_BUFFER_SIZE);
77             buffer.order(ByteOrder.nativeOrder());
78             ptr = getPtrOfBuffer(buffer);
79         }
80     }
81 
82     private static final ThreadLocal<ThreadLocalByteBuffer> threadLocal = new ThreadLocal<ThreadLocalByteBuffer>();
getThreadLocalBuffer()83     private static ThreadLocalByteBuffer getThreadLocalBuffer() {
84         ThreadLocalByteBuffer byteBuffer = threadLocal.get();
85         if (byteBuffer != null) return byteBuffer;
86 
87         byteBuffer = new ThreadLocalByteBuffer();
88         threadLocal.set(byteBuffer);
89         return byteBuffer;
90     }
91 
92     private final HashMap<Key, DoubleValue> nativeMap;
93     private final HashMap<Key, DoubleValue> changes;
94     private long cfDictionaryPtr;
95 
96     private long priorEncodedProperties;
97     private long currentEncodedProperties;
98     private final boolean flipped;
99 
JRSUIControl(final boolean flipped)100     public JRSUIControl(final boolean flipped){
101         this.flipped = flipped;
102         cfDictionaryPtr = getCFDictionary(flipped);
103         if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation");
104         nativeMap = new HashMap<Key, DoubleValue>();
105         changes = new HashMap<Key, DoubleValue>();
106     }
107 
JRSUIControl(final JRSUIControl other)108     JRSUIControl(final JRSUIControl other) {
109         flipped = other.flipped;
110         cfDictionaryPtr = getCFDictionary(flipped);
111         if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation");
112         nativeMap = new HashMap<Key, DoubleValue>();
113         changes = new HashMap<Key, DoubleValue>(other.nativeMap);
114         changes.putAll(other.changes);
115     }
116 
117     @SuppressWarnings("deprecation")
finalize()118     protected synchronized void finalize() throws Throwable {
119         if (cfDictionaryPtr == 0) return;
120         disposeCFDictionary(cfDictionaryPtr);
121         cfDictionaryPtr = 0;
122     }
123 
124 
125     enum BufferState {
126         NO_CHANGE,
127         ALL_CHANGES_IN_BUFFER,
128         SOME_CHANGES_IN_BUFFER,
129         CHANGE_WONT_FIT_IN_BUFFER;
130     }
131 
loadBufferWithChanges(final ThreadLocalByteBuffer localByteBuffer)132     private BufferState loadBufferWithChanges(final ThreadLocalByteBuffer localByteBuffer) {
133         final ByteBuffer buffer = localByteBuffer.buffer;
134         buffer.rewind();
135 
136         for (final JRSUIConstants.Key key : new HashSet<JRSUIConstants.Key>(changes.keySet())) {
137             final int changeIndex = buffer.position();
138             final JRSUIConstants.DoubleValue value = changes.get(key);
139 
140             try {
141                 buffer.putLong(key.getConstantPtr());
142                 buffer.put(value.getTypeCode());
143                 value.putValueInBuffer(buffer);
144             } catch (final BufferOverflowException e) {
145                 return handleBufferOverflow(buffer, changeIndex);
146             } catch (final RuntimeException e) {
147                 System.err.println(this);
148                 throw e;
149             }
150 
151             if (buffer.position() >= NIO_BUFFER_SIZE - 8) {
152                 return handleBufferOverflow(buffer, changeIndex);
153             }
154 
155             changes.remove(key);
156             nativeMap.put(key, value);
157         }
158 
159         buffer.putLong(0);
160         return BufferState.ALL_CHANGES_IN_BUFFER;
161     }
162 
handleBufferOverflow(final ByteBuffer buffer, final int changeIndex)163     private BufferState handleBufferOverflow(final ByteBuffer buffer, final int changeIndex) {
164         if (changeIndex == 0) {
165             buffer.putLong(0, 0);
166             return BufferState.CHANGE_WONT_FIT_IN_BUFFER;
167         }
168 
169         buffer.putLong(changeIndex, 0);
170         return BufferState.SOME_CHANGES_IN_BUFFER;
171     }
172 
set(final JRSUIConstants.Key key, final JRSUIConstants.DoubleValue value)173     private synchronized void set(final JRSUIConstants.Key key, final JRSUIConstants.DoubleValue value) {
174         final JRSUIConstants.DoubleValue existingValue = nativeMap.get(key);
175 
176         if (existingValue != null && existingValue.equals(value)) {
177             changes.remove(key);
178             return;
179         }
180 
181         changes.put(key, value);
182     }
183 
set(final JRSUIState state)184     public void set(final JRSUIState state) {
185         state.apply(this);
186     }
187 
setEncodedState(final long state)188     void setEncodedState(final long state) {
189         currentEncodedProperties = state;
190     }
191 
set(final JRSUIConstants.Key key, final double value)192     void set(final JRSUIConstants.Key key, final double value) {
193         set(key, new JRSUIConstants.DoubleValue(value));
194     }
195 
196 //    private static final Color blue = new Color(0x00, 0x00, 0xFF, 0x40);
197 //    private static void paintDebug(Graphics2D g, double x, double y, double w, double h) {
198 //        final Color prev = g.getColor();
199 //        g.setColor(blue);
200 //        g.drawRect((int)x, (int)y, (int)w, (int)h);
201 //        g.setColor(prev);
202 //    }
203 
204 //    private static int paintsWithNoChange = 0;
205 //    private static int paintsWithChangesThatFit = 0;
206 //    private static int paintsWithChangesThatOverflowed = 0;
207 
paint(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h)208     public void paint(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) {
209         paintImage(data, imgW, imgH, x, y, w, h);
210         priorEncodedProperties = currentEncodedProperties;
211     }
212 
paintImage(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h)213     private synchronized int paintImage(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) {
214         if (changes.isEmpty()) {
215 //            paintsWithNoChange++;
216             return paintImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h);
217         }
218 
219         final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
220         BufferState bufferState = loadBufferWithChanges(localByteBuffer);
221 
222         // fast tracking this, since it's the likely scenario
223         if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
224 //            paintsWithChangesThatFit++;
225             return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
226         }
227 
228         while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
229             final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
230             if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
231             bufferState = loadBufferWithChanges(localByteBuffer);
232         }
233 
234         if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
235             throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
236         }
237 
238         // implicitly ALL_CHANGES_IN_BUFFER, now that we sync'd the buffer down to native a few times
239 //        paintsWithChangesThatOverflowed++;
240         return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
241     }
242 
paint(final long cgContext, final double x, final double y, final double w, final double h)243     public void paint(final long cgContext, final double x, final double y, final double w, final double h) {
244         paintToCGContext(cgContext, x, y, w, h);
245         priorEncodedProperties = currentEncodedProperties;
246     }
247 
paintToCGContext(final long cgContext, final double x, final double y, final double w, final double h)248     private synchronized int paintToCGContext(final long cgContext, final double x, final double y, final double w, final double h) {
249         if (changes.isEmpty()) {
250 //            paintsWithNoChange++;
251             return paintToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h);
252         }
253 
254         final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
255         BufferState bufferState = loadBufferWithChanges(localByteBuffer);
256 
257         // fast tracking this, since it's the likely scenario
258         if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
259 //            paintsWithChangesThatFit++;
260             return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
261         }
262 
263         while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
264             final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
265             if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
266             bufferState = loadBufferWithChanges(localByteBuffer);
267         }
268 
269         if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
270             throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
271         }
272 
273         // implicitly ALL_CHANGES_IN_BUFFER, now that we sync'd the buffer down to native a few times
274 //        paintsWithChangesThatOverflowed++;
275         return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
276     }
277 
278 
getHitForPoint(final int x, final int y, final int w, final int h, final int hitX, final int hitY)279     Hit getHitForPoint(final int x, final int y, final int w, final int h, final int hitX, final int hitY) {
280         sync();
281         // reflect hitY about the midline of the control before sending to native
282         final Hit hit = JRSUIConstants.getHit(getNativeHitPart(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, hitX, 2 * y + h - hitY));
283         priorEncodedProperties = currentEncodedProperties;
284         return hit;
285     }
286 
getPartBounds(final double[] rect, final int x, final int y, final int w, final int h, final int part)287     void getPartBounds(final double[] rect, final int x, final int y, final int w, final int h, final int part) {
288         if (rect == null) throw new NullPointerException("Cannot load null rect");
289         if (rect.length != 4) throw new IllegalArgumentException("Rect must have four elements");
290 
291         sync();
292         getNativePartBounds(rect, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, part);
293         priorEncodedProperties = currentEncodedProperties;
294     }
295 
getScrollBarOffsetChange(final int x, final int y, final int w, final int h, final int offset, final int visibleAmount, final int extent)296     double getScrollBarOffsetChange(final int x, final int y, final int w, final int h, final int offset, final int visibleAmount, final int extent) {
297         sync();
298         final double offsetChange = getNativeScrollBarOffsetChange(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, offset, visibleAmount, extent);
299         priorEncodedProperties = currentEncodedProperties;
300         return offsetChange;
301     }
302 
sync()303     private void sync() {
304         if (changes.isEmpty()) return;
305 
306         final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
307         BufferState bufferState = loadBufferWithChanges(localByteBuffer);
308         if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
309             final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
310             if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
311             return;
312         }
313 
314         while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
315             final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
316             if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
317             bufferState = loadBufferWithChanges(localByteBuffer);
318         }
319 
320         if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
321             throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
322         }
323     }
324 
325     @Override
hashCode()326     public int hashCode() {
327         int bits = (int)(currentEncodedProperties ^ (currentEncodedProperties >>> 32));
328         bits ^= nativeMap.hashCode();
329         bits ^= changes.hashCode();
330         return bits;
331     }
332 
333     @Override
equals(final Object obj)334     public boolean equals(final Object obj) {
335         if (!(obj instanceof JRSUIControl)) return false;
336         final JRSUIControl other = (JRSUIControl)obj;
337         if (currentEncodedProperties != other.currentEncodedProperties) return false;
338         if (!nativeMap.equals(other.nativeMap)) return false;
339         if (!changes.equals(other.changes)) return false;
340         return true;
341     }
342 
343     @Override
toString()344     public String toString() {
345         final StringBuilder builder = new StringBuilder("JRSUIControl[inNative:");
346         builder.append(Arrays.toString(nativeMap.entrySet().toArray()));
347         builder.append(", changes:");
348         builder.append(Arrays.toString(changes.entrySet().toArray()));
349         builder.append("]");
350         return builder.toString();
351     }
352 }
353