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
26package apple.laf;
27
28import java.nio.*;
29import java.util.*;
30
31import apple.laf.JRSUIConstants.*;
32
33public final class JRSUIControl {
34    private static native int initNativeJRSUI();
35
36    private static native long getPtrOfBuffer(ByteBuffer byteBuffer);
37    private static native long getCFDictionary(boolean flipped);
38    private static native void disposeCFDictionary(long cfDictionaryPtr);
39
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
45    private static native int paintToCGContext                    (long cgContext,    long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h);
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
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);
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
51    private static native int getNativeHitPart(                            long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, double hitX, double hitY);
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);
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
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
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>();
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
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
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")
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
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
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
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
184    public void set(final JRSUIState state) {
185        state.apply(this);
186    }
187
188    void setEncodedState(final long state) {
189        currentEncodedProperties = state;
190    }
191
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
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
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
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
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
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
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
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
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
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
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
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