/** * Copyright (C) Zhang,Yuexiang (xfeep) * */ package nginx.clojure.util; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import nginx.clojure.HackUtils; import nginx.clojure.MiniConstants; import nginx.clojure.NginxClojureRT; public class NginxSharedHashMap<K, V> implements ConcurrentMap<K, V>{ public final static int NGX_CLOJURE_SHARED_MAP_OK = 0; public final static int NGX_CLOJURE_SHARED_MAP_OUT_OF_MEM = 1; public final static int NGX_CLOJURE_SHARED_MAP_NOT_FOUND = 2; public final static int NGX_CLOJURE_SHARED_MAP_INVLAID_KEY_TYPE = 3; public final static int NGX_CLOJURE_SHARED_MAP_INVLAID_VALUE_TYPE = 4; public final static int NGX_CLOJURE_SHARED_MAP_JINT = 0; public final static int NGX_CLOJURE_SHARED_MAP_JLONG = 1; public final static int NGX_CLOJURE_SHARED_MAP_JSTRING = 2; public final static int NGX_CLOJURE_SHARED_MAP_JBYTEA = 3; public final static int NGX_CLOJURE_SHARED_MAP_JOBJECT = 4; private long ctx; private String name; private long nullVal = 0; //shared hashmap private native static long ngetMap(Object nameBuf, long offset, long len); private native static Object nget(long ctx, int ktype, Object keyBuf, long offset, long len); private native static Object nput(long ctx, int ktype, Object keyBuf, long keyOffset, long keyLen, int vtype, Object valBuf, long valueOffset, long valLen); private native static Object nputIfAbsent(long ctx, int ktype, Object keyBuf, long keyOffset, long keyLen, int vtype, Object valBuf, long valueOffset, long valLen); private native static Object nremove(long ctx, int ktype, Object keyBuf, long offset, long len); private native static long ndelete(long ctx, int ktype, Object keyBuf, long offset, long len); private native static long nsize(long ctx); private native static long nclear(long ctx); private native static long ncontains(long ctx, int ktype, Object keyBuf, long offset, long len); private native static long ngetNumber(long ctx, int ktype, Object keyBuf, long offset, long len, int vtype); private native static long nputNumber(long ctx, int ktype, Object keyBuf, long keyOffset, long keyLen, int vtype, long val, long nullVal); private native static long nputNumberIfAbsent(long ctx, int ktype, Object keyBuf, long keyOffset, long keyLen, int vtype, long val, long nullVal); private native static long nremoveNumber(long ctx, int ktype, Object keyBuf, long offset, long len, int vtype, long nullVal); private native static long natomicAddNumber(long ctx, int ktype, Object keyBuf, long offset, long len, int vtype, long delta); private native static long nvisit(long ctx, SharedMapSimpleVisitor visitor); private NginxSharedHashMap() { } public NginxSharedHashMap(String name) { ByteBuffer bb = HackUtils.encode(name, MiniConstants.DEFAULT_ENCODING, NginxClojureRT.pickByteBuffer()); long ctx = ngetMap(bb.array(), MiniConstants.BYTE_ARRAY_OFFSET, bb.remaining()); if (ctx == 0) { throw new RuntimeException("can not find shared map whose name is " + name); } this.name = name; this.ctx = ctx; } @SuppressWarnings("restriction") private final static Object native2JavaObject(int type, long addr, long size) { switch (type) { case NGX_CLOJURE_SHARED_MAP_JINT: return HackUtils.UNSAFE.getInt(addr); case NGX_CLOJURE_SHARED_MAP_JLONG: return HackUtils.UNSAFE.getLong(addr); case NGX_CLOJURE_SHARED_MAP_JSTRING: return NginxClojureRT.fetchDString(addr, (int)size); case NGX_CLOJURE_SHARED_MAP_JBYTEA: byte[] ba = new byte[(int)size]; NginxClojureRT.ngx_http_clojure_mem_copy_to_obj(addr, ba, MiniConstants.BYTE_ARRAY_OFFSET, size); return ba; default: //TODO: POJO deserialization throw new RuntimeException("unsupported type:" + type); } } public interface SharedMapSimpleVisitor { public int visit(Object key, Object val); } private final static int visit(int ktype, long kaddr, long ksize, int vtype, long vaddr, long vsize, SharedMapSimpleVisitor visitor) { return visitor.visit(native2JavaObject(ktype, kaddr, ksize), native2JavaObject(vtype, vaddr, vsize)); } public static <KT, VT> NginxSharedHashMap<KT, VT> build(String name) { return new NginxSharedHashMap(name); } public void setNullVal(long nullVal) { this.nullVal = nullVal; } @Override public int size() { return (int) nsize(ctx); } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean containsKey(Object key) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return ncontains(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining()) == NGX_CLOJURE_SHARED_MAP_OK; } @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException("containsValue"); } private int buildType(Object o) { if (o == null) { throw new NullPointerException("null object not supported!"); } if (o instanceof Integer) { return NGX_CLOJURE_SHARED_MAP_JINT; } else if (o instanceof Long) { return NGX_CLOJURE_SHARED_MAP_JLONG; } else if (o instanceof String) { return NGX_CLOJURE_SHARED_MAP_JSTRING; } else if (o instanceof byte[]) { return NGX_CLOJURE_SHARED_MAP_JBYTEA; } else { throw new UnsupportedOperationException("type " + o.getClass() + " not supported!"); } } private ByteBuffer buildKeyBuffer(int ktype, Object key) { ByteBuffer kb = NginxClojureRT.pickByteBuffer(); switch(ktype) { case NGX_CLOJURE_SHARED_MAP_JINT: Integer ik = (Integer) key; kb.order(ByteOrder.nativeOrder()); kb.putInt(ik.intValue()); kb.flip(); break; case NGX_CLOJURE_SHARED_MAP_JLONG: Long lk = (Long)key; kb.order(ByteOrder.nativeOrder()); kb.putLong(lk.longValue()); kb.flip(); break; case NGX_CLOJURE_SHARED_MAP_JSTRING: kb = HackUtils.encode((String)key, MiniConstants.DEFAULT_ENCODING, kb); break; case NGX_CLOJURE_SHARED_MAP_JBYTEA: kb = ByteBuffer.wrap((byte[])key); break; default: //TODO: POJO serialization throw new UnsupportedOperationException("key type " + ktype + " not supported!"); } return kb; } @SuppressWarnings("unchecked") @Override public V get(Object key) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (V)nget(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining()); } @SuppressWarnings("unchecked") @Override public V put(K key, V val) { int ktype = buildType(key); int vtype; ByteBuffer kb = buildKeyBuffer(ktype, key); ByteBuffer vb; if (val instanceof Integer) { vtype = NGX_CLOJURE_SHARED_MAP_JINT; long rt = nputNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, ((Integer) val).longValue(), nullVal); if (rt == nullVal) { return null; } return (V) (Integer)(int)(rt); } else if (val instanceof Long) { vtype = NGX_CLOJURE_SHARED_MAP_JLONG; long rt = nputNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, ((Long) val).longValue(), nullVal); if (rt == nullVal) { return null; } return (V)(Long)rt; } else if (val instanceof String) { String value = (String)val; vtype = NGX_CLOJURE_SHARED_MAP_JSTRING; if (kb.capacity() - kb.remaining() >= value.length()) { kb.position(kb.remaining()); kb.limit(kb.capacity()); vb = kb.slice(); kb.flip(); }else { vb = ByteBuffer.allocate(value.length()); } vb = HackUtils.encode(value, MiniConstants.DEFAULT_ENCODING, vb); return (V)nput(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, vb.array(), MiniConstants.BYTE_ARRAY_OFFSET + vb.arrayOffset() + vb.position(), vb.remaining()); } else if (val instanceof byte[]) { vtype = NGX_CLOJURE_SHARED_MAP_JBYTEA; return (V)nput(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, val, MiniConstants.BYTE_ARRAY_OFFSET, ((byte[])val).length); } else { throw new UnsupportedOperationException("val type : " + key.getClass() + ", not supported!"); } } @SuppressWarnings("unchecked") @Override public V remove(Object key) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (V)nremove(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining()); } public boolean delete(Object key) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return ndelete(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining()) == NGX_CLOJURE_SHARED_MAP_OK; } public int getInt(Object key) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (int)ngetNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JINT); } public int putInt(K key, int val) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (int)nputNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JINT, val, nullVal); } public int putIntIfAbsent(K key, int val) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (int)nputNumberIfAbsent(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JINT, val, nullVal); } /** * Atomic update value to `old_value + delta` and returns the old value. * If the key is not found or the value type is not int RuntimeException will be thrown. * @return old int value */ public int atomicAddInt(K key, int delta) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (int)natomicAddNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JINT, delta); } /** * Atomic update value to `old_value + delta` and returns the old value. * If the key is not found or the value type is not long RuntimeException will be thrown. * @return old long value */ public long atomicAddLong(K key, long delta) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return (int)natomicAddNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JLONG, delta); } public long getLong(Object key) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return ngetNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JLONG); } public long putLong(K key, long val) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return nputNumber(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JLONG, val, nullVal); } public long putLongIfAbsent(K key, long val) { int ktype = buildType(key); ByteBuffer kb = buildKeyBuffer(ktype, key); return nputNumberIfAbsent(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), NGX_CLOJURE_SHARED_MAP_JLONG, val, nullVal); } @Override public void clear() { long rc = nclear(ctx); if (rc != 0) { throw new RuntimeException("unexcepted error, rc=" + rc); } } @Override public Set<K> keySet() { NginxClojureRT.getLog().warn("NginxSharedHashMap.keySet is quite expensive operation DO NOT use it at non-debug case!!!"); final Set sets = new HashSet(); nvisit(ctx, new SharedMapSimpleVisitor() { @Override public int visit(Object key, Object val) { sets.add(key); return 0; } }); return sets; } @Override public Collection<V> values() { NginxClojureRT.getLog().warn("NginxSharedHashMap.values is quite expensive operation DO NOT use it at non-debug case!!!"); final List vals = new ArrayList(); nvisit(ctx, new SharedMapSimpleVisitor() { @Override public int visit(Object key, Object val) { vals.add(val); return 0; } }); return vals; } @Override public Set<java.util.Map.Entry<K, V>> entrySet() { NginxClojureRT.getLog().warn("NginxSharedHashMap.entrySet is quite expensive operation DO NOT use it at non-debug case!!!"); final Set<java.util.Map.Entry<K, V>> sets = new HashSet<Map.Entry<K,V>>(); nvisit(ctx, new SharedMapSimpleVisitor() { @Override public int visit(Object key, Object val) { SimpleEntry en = new SimpleEntry(key, val); sets.add(en); return 0; } }); return sets; } /* (non-Javadoc) * @see java.util.Map#putAll(java.util.Map) */ @Override public void putAll(Map<? extends K, ? extends V> m) { for (Entry<? extends K, ? extends V> en : m.entrySet()) { put(en.getKey(), en.getValue()); } } @SuppressWarnings("unchecked") @Override public V putIfAbsent(K key, V val) { int ktype = buildType(key); int vtype; ByteBuffer kb = buildKeyBuffer(ktype, key); ByteBuffer vb; if (val instanceof Integer) { vtype = NGX_CLOJURE_SHARED_MAP_JINT; long rt = nputNumberIfAbsent(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, ((Integer) val).longValue(), nullVal); if (rt == nullVal) { return null; } return (V) (Integer)(int)(rt); } else if (val instanceof Long) { vtype = NGX_CLOJURE_SHARED_MAP_JLONG; long rt = nputNumberIfAbsent(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, ((Long) val).longValue(), nullVal); if (rt == nullVal) { return null; } return (V)(Long)rt; } else if (val instanceof String) { String value = (String)val; vtype = NGX_CLOJURE_SHARED_MAP_JSTRING; if (kb.capacity() - kb.remaining() >= value.length()) { kb.position(kb.remaining()); kb.limit(kb.capacity()); vb = kb.slice(); kb.flip(); }else { vb = ByteBuffer.allocate(value.length()); } vb = HackUtils.encode(value, MiniConstants.DEFAULT_ENCODING, vb); return (V)nputIfAbsent(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, vb.array(), MiniConstants.BYTE_ARRAY_OFFSET + vb.arrayOffset() + vb.position(), vb.remaining()); } else if (val instanceof byte[]) { vtype = NGX_CLOJURE_SHARED_MAP_JBYTEA; return (V)nputIfAbsent(ctx, ktype, kb.array(), MiniConstants.BYTE_ARRAY_OFFSET, kb.remaining(), vtype, val, MiniConstants.BYTE_ARRAY_OFFSET, ((byte[])val).length); } else { throw new UnsupportedOperationException("val type : " + key.getClass() + ", not supported!"); } } @Override public boolean remove(Object key, Object value) { throw new UnsupportedOperationException("boolean remove(Object key, Object value)"); } @Override public boolean replace(K key, V oldValue, V newValue) { throw new UnsupportedOperationException("boolean replace(K key, V oldValue, V newValue)"); } @Override public V replace(K key, V value) { throw new UnsupportedOperationException("V replace(K key, V value"); } public String getName() { return name; } }