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