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