package apple.laf;
import java.nio.*;
import java.util.*;
import apple.laf.JRSUIConstants.*;
public final class JRSUIControl {
private static native int initNativeJRSUI();
private static native long getPtrOfBuffer(ByteBuffer byteBuffer);
private static native long getCFDictionary(boolean flipped);
private static native void disposeCFDictionary(long cfDictionaryPtr);
private static native int syncChanges(long cfDictionaryPtr, long byteBufferPtr);
private static native int paintToCGContext (long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h);
private static native int paintChangesToCGContext (long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr);
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);
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);
private static native int getNativeHitPart( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, double hitX, double hitY);
private static native void getNativePartBounds(final double[] rect, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int part);
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);
private static final int INCOHERENT = 2;
private static final int NOT_INIT = 1;
private static final int SUCCESS = 0;
private static final int NULL_PTR = -1;
private static final int NULL_CG_REF = -2;
private static int nativeJRSInitialized = NOT_INIT;
public static void initJRSUI() {
if (nativeJRSInitialized == SUCCESS) return;
nativeJRSInitialized = initNativeJRSUI();
if (nativeJRSInitialized != SUCCESS) throw new RuntimeException("JRSUI could not be initialized (" + nativeJRSInitialized + ").");
}
private static final int NIO_BUFFER_SIZE = 128;
private static class ThreadLocalByteBuffer {
final ByteBuffer buffer;
final long ptr;
public ThreadLocalByteBuffer() {
buffer = ByteBuffer.allocateDirect(NIO_BUFFER_SIZE);
buffer.order(ByteOrder.nativeOrder());
ptr = getPtrOfBuffer(buffer);
}
}
private static final ThreadLocal<ThreadLocalByteBuffer> threadLocal = new ThreadLocal<ThreadLocalByteBuffer>();
private static ThreadLocalByteBuffer getThreadLocalBuffer() {
ThreadLocalByteBuffer byteBuffer = threadLocal.get();
if (byteBuffer != null) return byteBuffer;
byteBuffer = new ThreadLocalByteBuffer();
threadLocal.set(byteBuffer);
return byteBuffer;
}
private final HashMap<Key, DoubleValue> nativeMap;
private final HashMap<Key, DoubleValue> changes;
private long cfDictionaryPtr;
private long priorEncodedProperties;
private long currentEncodedProperties;
private final boolean flipped;
public JRSUIControl(final boolean flipped){
this.flipped = flipped;
cfDictionaryPtr = getCFDictionary(flipped);
if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation");
nativeMap = new HashMap<Key, DoubleValue>();
changes = new HashMap<Key, DoubleValue>();
}
JRSUIControl(final JRSUIControl other) {
flipped = other.flipped;
cfDictionaryPtr = getCFDictionary(flipped);
if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation");
nativeMap = new HashMap<Key, DoubleValue>();
changes = new HashMap<Key, DoubleValue>(other.nativeMap);
changes.putAll(other.changes);
}
@SuppressWarnings("deprecation")
protected synchronized void finalize() throws Throwable {
if (cfDictionaryPtr == 0) return;
disposeCFDictionary(cfDictionaryPtr);
cfDictionaryPtr = 0;
}
enum BufferState {
NO_CHANGE,
ALL_CHANGES_IN_BUFFER,
SOME_CHANGES_IN_BUFFER,
CHANGE_WONT_FIT_IN_BUFFER;
}
private BufferState loadBufferWithChanges(final ThreadLocalByteBuffer localByteBuffer) {
final ByteBuffer buffer = localByteBuffer.buffer;
buffer.rewind();
for (final JRSUIConstants.Key key : new HashSet<JRSUIConstants.Key>(changes.keySet())) {
final int changeIndex = buffer.position();
final JRSUIConstants.DoubleValue value = changes.get(key);
try {
buffer.putLong(key.getConstantPtr());
buffer.put(value.getTypeCode());
value.putValueInBuffer(buffer);
} catch (final BufferOverflowException e) {
return handleBufferOverflow(buffer, changeIndex);
} catch (final RuntimeException e) {
System.err.println(this);
throw e;
}
if (buffer.position() >= NIO_BUFFER_SIZE - 8) {
return handleBufferOverflow(buffer, changeIndex);
}
changes.remove(key);
nativeMap.put(key, value);
}
buffer.putLong(0);
return BufferState.ALL_CHANGES_IN_BUFFER;
}
private BufferState handleBufferOverflow(final ByteBuffer buffer, final int changeIndex) {
if (changeIndex == 0) {
buffer.putLong(0, 0);
return BufferState.CHANGE_WONT_FIT_IN_BUFFER;
}
buffer.putLong(changeIndex, 0);
return BufferState.SOME_CHANGES_IN_BUFFER;
}
private synchronized void set(final JRSUIConstants.Key key, final JRSUIConstants.DoubleValue value) {
final JRSUIConstants.DoubleValue existingValue = nativeMap.get(key);
if (existingValue != null && existingValue.equals(value)) {
changes.remove(key);
return;
}
changes.put(key, value);
}
public void set(final JRSUIState state) {
state.apply(this);
}
void setEncodedState(final long state) {
currentEncodedProperties = state;
}
void set(final JRSUIConstants.Key key, final double value) {
set(key, new JRSUIConstants.DoubleValue(value));
}
public void paint(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) {
paintImage(data, imgW, imgH, x, y, w, h);
priorEncodedProperties = currentEncodedProperties;
}
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) {
if (changes.isEmpty()) {
return paintImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h);
}
final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
BufferState bufferState = loadBufferWithChanges(localByteBuffer);
if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
bufferState = loadBufferWithChanges(localByteBuffer);
}
if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
}
return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
public void paint(final long cgContext, final double x, final double y, final double w, final double h) {
paintToCGContext(cgContext, x, y, w, h);
priorEncodedProperties = currentEncodedProperties;
}
private synchronized int paintToCGContext(final long cgContext, final double x, final double y, final double w, final double h) {
if (changes.isEmpty()) {
return paintToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h);
}
final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
BufferState bufferState = loadBufferWithChanges(localByteBuffer);
if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
bufferState = loadBufferWithChanges(localByteBuffer);
}
if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
}
return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
Hit getHitForPoint(final int x, final int y, final int w, final int h, final int hitX, final int hitY) {
sync();
final Hit hit = JRSUIConstants.getHit(getNativeHitPart(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, hitX, 2 * y + h - hitY));
priorEncodedProperties = currentEncodedProperties;
return hit;
}
void getPartBounds(final double[] rect, final int x, final int y, final int w, final int h, final int part) {
if (rect == null) throw new NullPointerException("Cannot load null rect");
if (rect.length != 4) throw new IllegalArgumentException("Rect must have four elements");
sync();
getNativePartBounds(rect, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, part);
priorEncodedProperties = currentEncodedProperties;
}
double getScrollBarOffsetChange(final int x, final int y, final int w, final int h, final int offset, final int visibleAmount, final int extent) {
sync();
final double offsetChange = getNativeScrollBarOffsetChange(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, offset, visibleAmount, extent);
priorEncodedProperties = currentEncodedProperties;
return offsetChange;
}
private void sync() {
if (changes.isEmpty()) return;
final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
BufferState bufferState = loadBufferWithChanges(localByteBuffer);
if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
return;
}
while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
bufferState = loadBufferWithChanges(localByteBuffer);
}
if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
}
}
@Override
public int hashCode() {
int bits = (int)(currentEncodedProperties ^ (currentEncodedProperties >>> 32));
bits ^= nativeMap.hashCode();
bits ^= changes.hashCode();
return bits;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof JRSUIControl)) return false;
final JRSUIControl other = (JRSUIControl)obj;
if (currentEncodedProperties != other.currentEncodedProperties) return false;
if (!nativeMap.equals(other.nativeMap)) return false;
if (!changes.equals(other.changes)) return false;
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder("JRSUIControl[inNative:");
builder.append(Arrays.toString(nativeMap.entrySet().toArray()));
builder.append(", changes:");
builder.append(Arrays.toString(changes.entrySet().toArray()));
builder.append("]");
return builder.toString();
}
}