/**
  * Creates a new UnsafeMetricDefinition
  *
  * @param globalId The global id of the metric
  * @param timestamp The creation timestamp of this metric, or -1L if the metric is new, in which
  *     case the currnt time will be substituted
  * @param name The metric name
  * @param opaqueKey The opaque key
  */
 public UnsafeMetricDefinition(long globalId, long timestamp, String name, byte[] opaqueKey) {
   byte[] nameBytes = getBytes(name);
   byte[] opaqueBytes = getBytes(opaqueKey);
   int size = BASE_SIZE + nameBytes.length + opaqueBytes.length;
   address[0] = UnsafeAdapter.allocateAlignedMemory(size);
   UnsafeAdapter.registerForDeAlloc(this);
   UnsafeAdapter.putByte(address[0], DELETE_FLAG);
   UnsafeAdapter.putInt(address[0] + SIZE, size);
   UnsafeAdapter.putLong(address[0] + ID, globalId);
   UnsafeAdapter.putLong(
       address[0] + TIMESTAMP, timestamp != -1L ? timestamp : System.currentTimeMillis());
   UnsafeAdapter.putInt(address[0] + NAME_SIZE, nameBytes.length);
   UnsafeAdapter.putInt(address[0] + OPAQUE_SIZE, opaqueBytes.length);
   if (nameBytes.length > 0) {
     UnsafeAdapter.copyMemory(
         nameBytes,
         UnsafeAdapter.BYTE_ARRAY_OFFSET,
         null,
         address[0] + NAME_BYTES,
         nameBytes.length);
   }
   if (opaqueBytes.length > 0) {
     UnsafeAdapter.copyMemory(
         opaqueBytes,
         UnsafeAdapter.BYTE_ARRAY_OFFSET,
         null,
         address[0] + NAME_BYTES + nameBytes.length,
         opaqueBytes.length);
   }
 }
 /**
  * Returns the bytes for the name
  *
  * @return the bytes for the name
  */
 public byte[] getNameBytes() {
   if (address[0] == -1L) return null;
   int size = getNameSize();
   if (size == 0) return EMPTY_BYTE_ARR;
   byte[] bytes = new byte[size];
   UnsafeAdapter.copyMemory(
       null, address[0] + NAME_BYTES, bytes, UnsafeAdapter.BYTE_ARRAY_OFFSET, size);
   return bytes;
 }
 /**
  * {@inheritDoc}
  *
  * @see org.helios.rindle.metric.IMetricDefinition#getOpaqueKey()
  */
 @Override
 public byte[] getOpaqueKey() {
   int size = getOpaqueSize();
   if (size == 0) return null;
   byte[] bytes = new byte[size];
   UnsafeAdapter.copyMemory(
       null,
       address[0] + NAME_BYTES + getNameSize(),
       bytes,
       UnsafeAdapter.BYTE_ARRAY_OFFSET,
       size);
   return bytes;
 }
 /**
  * {@inheritDoc}
  *
  * @see org.helios.rindle.metric.IMetricDefinition#getCreatedTimestamp()
  */
 @Override
 public long getCreatedTimestamp() {
   if (address[0] == -1L) return IMetricDefinition.NO_ENTRY_VALUE;
   return UnsafeAdapter.getLong(address[0] + TIMESTAMP);
 }
 /**
  * Updates the byte size of this metric
  *
  * @return the new byte size of this metric
  */
 protected int updateByteSize() {
   int size = BASE_SIZE + getNameSize() + getOpaqueSize();
   UnsafeAdapter.putInt(address[0] + SIZE, size);
   return size;
 }
 /**
  * {@inheritDoc}
  *
  * @see org.helios.rindle.metric.IMetricDefinition#getId()
  */
 @Override
 public long getId() {
   if (address[0] == -1L) return IMetricDefinition.NO_ENTRY_VALUE;
   return UnsafeAdapter.getLong(address[0] + ID);
 }
 /**
  * Returns the byte size of this metric
  *
  * @return the byte size of this metric
  */
 protected int getByteSize() {
   if (address[0] == -1L) return -1;
   return UnsafeAdapter.getInt(address[0] + SIZE);
 }
 /** Touches the metric created timestamp */
 protected void touch() {
   UnsafeAdapter.putLong(address[0] + TIMESTAMP, System.currentTimeMillis());
 }
 /**
  * Updates the id of this metric
  *
  * @param id the new id
  */
 protected void setId(long id) {
   UnsafeAdapter.putLong(address[0] + ID, id);
 }
 /**
  * Returns the size of the opaque key
  *
  * @return the size of the opaque key
  */
 protected int getOpaqueSize() {
   if (address[0] == -1L) return -1;
   return UnsafeAdapter.getInt(address[0] + OPAQUE_SIZE);
 }
/**
 * Title: CharBufferStringKeyCache
 *
 * <p>Description: An {@link IStringKeyCache} implementation using {@link CharBuffer}s
 *
 * <p>Company: Helios Development Group LLC
 *
 * @author Whitehead (nwhitehead AT heliosdev DOT org)
 *     <p><code>org.helios.rindle.store.CharBufferStringKeyCache</code>
 */
public class CharBufferStringKeyCache implements IStringKeyCache {

  /** The spin lock */
  protected final SpinLock lock = UnsafeAdapter.allocateSpinLock();

  /** The cache of Chronicle entry ids keyed by the long hash code of the name */
  private final TObjectLongHashMap<CharBuffer> cache;

  private final boolean offHeap;

  /**
   * Creates a new CharBufferStringKeyCache
   *
   * @param initialCapacity used to find a prime capacity for the table.
   * @param loadFactor used to calculate the threshold over which rehashing takes place.
   * @param offHeap If true, buffer is placed off-heap, on-heap otherwise
   */
  public CharBufferStringKeyCache(int initialCapacity, float loadFactor, boolean offHeap) {
    cache = new TObjectLongHashMap<CharBuffer>(initialCapacity, loadFactor, NO_ENTRY_VALUE);
    this.offHeap = offHeap;
  }

  /**
   * Creates a new on-heap CharBufferStringKeyCache with the default load factor
   *
   * @param initialCapacity used to find a prime capacity for the table.
   */
  public CharBufferStringKeyCache(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR, false);
  }

  /**
   * Wraps the passed key in a CharBuffer
   *
   * @param key The key to wrap
   * @return The CharBuffer wrapped key
   */
  protected CharBuffer wrap(CharSequence key) {
    CharBuffer cb = null;
    if (!offHeap) {
      cb = ((CharBuffer) CharBuffer.allocate(key.length()).append(key).flip()).asReadOnlyBuffer();
    } else {
      cb =
          ((CharBuffer)
                  ByteBuffer.allocateDirect(key.length() * 2).asCharBuffer().append(key).flip())
              .asReadOnlyBuffer();
    }
    return cb;
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#size()
   */
  @Override
  public int size() {
    try {
      lock.xlock();
      return cache.size();
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#containsKey(java.lang.CharSequence)
   */
  @Override
  public boolean containsKey(CharSequence key) {
    if (key == null) return false;
    try {
      lock.xlock();
      if (cache.isEmpty()) return false;
      return cache.containsKey(wrap(key));
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#clear()
   */
  @Override
  public void clear() {
    try {
      lock.xlock();
      cache.clear();
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IKeyCache#purge()
   */
  @Override
  public void purge() {
    try {
      lock.xlock();
      cache.clear();
      cache.trimToSize();
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#get(java.lang.CharSequence)
   */
  @Override
  public long get(CharSequence key) {
    if (key == null) return NO_ENTRY_VALUE;
    try {
      lock.xlock();
      if (cache.isEmpty()) return NO_ENTRY_VALUE;
      return cache.get(wrap(key));
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#put(java.lang.CharSequence, long)
   */
  @Override
  public long put(CharSequence key, long value) {
    if (key == null) throw new IllegalArgumentException("The passed key was null");
    try {
      lock.xlock();
      return cache.put(wrap(key), value);
    } finally {
      lock.xunlock();
    }
  }

  /**
   * Unguarded direct put for bulk puts
   *
   * @param key The key
   * @param value The long value
   * @return the previous value associated with they key or {@link #NO_ENTRY_VALUE} if there was no
   *     mapping for the key.
   */
  protected long _put(CharSequence key, long value) {
    return cache.put(wrap(key), value);
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#putIfAbsent(java.lang.CharSequence,
   *     long)
   */
  @Override
  public long putIfAbsent(CharSequence key, long value) {
    if (key == null) throw new IllegalArgumentException("The passed key was null");
    try {
      lock.xlock();
      return cache.putIfAbsent(wrap(key), value);
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#remove(java.lang.CharSequence)
   */
  @Override
  public long remove(CharSequence key) {
    if (key == null) throw new IllegalArgumentException("The passed key was null");
    try {
      lock.xlock();
      return cache.remove(wrap(key));
    } finally {
      lock.xunlock();
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see org.helios.rindle.store.chronicle.IStringKeyCache#putAll(java.util.Map)
   */
  @Override
  public void putAll(Map<? extends CharSequence, ? extends Long> map) {
    if (map == null) throw new IllegalArgumentException("The passed map was null");
    if (map.isEmpty()) return;
    try {
      lock.xlock();
      for (Map.Entry<? extends CharSequence, ? extends Long> entry : map.entrySet()) {
        _put(entry.getKey(), entry.getValue().longValue());
      }
    } finally {
      lock.xunlock();
    }
  }

  /**
   * Adjusts the primitive value mapped to the key if the key is present in the map.
   *
   * @param key The stringy key
   * @param value The value
   * @return true if a mapping was found and modified.
   * @see gnu.trove.map.hash.TObjectLongHashMap#adjustValue(java.lang.Object, long)
   */
  public boolean adjustValue(CharSequence key, long value) {
    if (key == null) throw new IllegalArgumentException("The passed key was null");
    try {
      lock.xlock();
      return cache.adjustValue(wrap(key), value);
    } finally {
      lock.xunlock();
    }
  }

  /**
   * Compresses the cache to the minimum prime size
   *
   * @see gnu.trove.impl.hash.THash#trimToSize()
   */
  public final void trimToSize() {
    try {
      lock.xlock();
      cache.trimToSize();
    } finally {
      lock.xunlock();
    }
  }

  /**
   * The auto-compaction factor controls whether and when a table performs a compaction
   * automatically after a certain number of remove operations. If the value is non-zero, the number
   * of removes that need to occur for auto-compaction is the size of table at the time of the
   * previous compaction (or the initial capacity) multiplied by this factor.
   *
   * <p>Setting this value to zero will disable auto-compaction.
   *
   * @param factor a <tt>float</tt> that indicates the auto-compaction factor
   * @see gnu.trove.impl.hash.THash#setAutoCompactionFactor(float)
   */
  public void setAutoCompactionFactor(float factor) {
    try {
      lock.xlock();
      cache.setAutoCompactionFactor(factor);
    } finally {
      lock.xunlock();
    }
  }

  /**
   * Returns the cache's auto compaction factor
   *
   * @return a <<tt>float</tt> that represents the auto-compaction factor.
   * @see gnu.trove.impl.hash.THash#getAutoCompactionFactor()
   */
  public float getAutoCompactionFactor() {
    try {
      lock.xlock();
      return cache.getAutoCompactionFactor();
    } finally {
      lock.xunlock();
    }
  }

  /**
   * Temporarily disables auto-compaction. MUST be followed by calling {@link
   * #reenableAutoCompaction}.
   *
   * @see gnu.trove.impl.hash.THash#tempDisableAutoCompaction()
   */
  public void tempDisableAutoCompaction() {
    try {
      lock.xlock();
      cache.tempDisableAutoCompaction();
    } finally {
      lock.xunlock();
    }
  }

  /**
   * Re-enable auto-compaction after it was disabled via {@link #tempDisableAutoCompaction()}.
   *
   * @param check_for_compaction True if compaction should be performed if needed before returning.
   *     If false, no compaction will be performed.
   * @see gnu.trove.impl.hash.THash#reenableAutoCompaction(boolean)
   */
  public void reenableAutoCompaction(boolean check_for_compaction) {
    try {
      lock.xlock();
      cache.reenableAutoCompaction(check_for_compaction);
    } finally {
      lock.xunlock();
    }
  }
}