content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.cassandra.io.util;
  
  import java.lang.reflect.Field;
  import java.nio.ByteBuffer;
  
  import net.nicoulaj.compilecommand.annotations.Inline;
  
  import org.apache.cassandra.utils.FastByteOperations;
  import org.apache.cassandra.utils.concurrent.Ref;
  import org.apache.cassandra.utils.memory.LittleEndianMemoryUtil;
  import org.apache.cassandra.utils.memory.MemoryUtil;
  import sun.misc.Unsafe;
  
  /**
   * An off-heap region of memory that must be manually free'd when no longer needed.
   * It uses Little Endian (LE).
   */
  public class Memory implements AutoCloseable, ReadableMemory
  {
      private static final Unsafe unsafe;
      static
      {
          try
          {
              Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
              field.setAccessible(true);
              unsafe = (sun.misc.Unsafe) field.get(null);
          }
          catch (Exception e)
          {
              throw new AssertionError(e);
          }
      }
  
      private static final long BYTE_ARRAY_BASE_OFFSET = unsafe.arrayBaseOffset(byte[].class);
  
      public static final ByteBuffer[] NO_BYTE_BUFFERS = new ByteBuffer[0];
  
      protected long peer;
      // size of the memory region
      protected final long size;
  
      protected Memory(long bytes)
      {
          if (bytes <= 0)
              throw new AssertionError();
          size = bytes;
          peer = MemoryUtil.allocate(size);
          // we permit a 0 peer iff size is zero, since such an allocation makes no sense, and an allocator would be
          // justified in returning a null pointer (and permitted to do so: http://www.cplusplus.com/reference/cstdlib/malloc)
          if (peer == 0)
              throw new OutOfMemoryError(); // checkstyle: permit this instantiation
      }
  
      // create a memory object that references the exacy same memory location as the one provided.
      // this should ONLY be used by SafeMemory
      protected Memory(Memory copyOf)
      {
          size = copyOf.size;
          peer = copyOf.peer;
      }
  
      public static Memory allocate(long bytes)
      {
          if (bytes < 0)
              throw new IllegalArgumentException();
  
          if (Ref.TRACE_ENABLED)
              return new SafeMemory(bytes);
  
          return new Memory(bytes);
      }
  
      public void setByte(long offset, byte b)
      {
          checkBounds(offset, offset + 1);
          LittleEndianMemoryUtil.setByte(peer + offset, b);
      }
  
      public void setMemory(long offset, long bytes, byte b)
      {
          checkBounds(offset, offset + bytes);
          // check if the last element will fit into the memory
          unsafe.setMemory(peer + offset, bytes, b);
      }
  
      public void setLong(long offset, long l)
      {
          checkBounds(offset, offset + 8);
          LittleEndianMemoryUtil.setLong(peer + offset, l);
      }
  
      public void setInt(long offset, int l)
      {
          checkBounds(offset, offset + 4);
          LittleEndianMemoryUtil.setInt(peer + offset, l);
      }
  
      public void setBytes(long memoryOffset, ByteBuffer buffer)
      {
          if (buffer == null)
              throw new NullPointerException();
          else if (buffer.remaining() == 0)
              return;
  
          checkBounds(memoryOffset, memoryOffset + buffer.remaining());
          if (buffer.hasArray())
          {
              setBytes(memoryOffset, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
          }
          else if (buffer.isDirect())
          {
              unsafe.copyMemory(MemoryUtil.getAddress(buffer) + buffer.position(), peer + memoryOffset, buffer.remaining());
          }
          else
              throw new IllegalStateException();
      }
      /**
       * Transfers count bytes from buffer to Memory
       *
       * @param memoryOffset start offset in the memory
       * @param buffer the data buffer
       * @param bufferOffset start offset of the buffer
       * @param count number of bytes to transfer
       */
      public void setBytes(long memoryOffset, byte[] buffer, int bufferOffset, int count)
      {
          if (buffer == null)
              throw new NullPointerException();
          else if (bufferOffset < 0
                   || count < 0
                   || bufferOffset + count > buffer.length)
              throw new IndexOutOfBoundsException();
          else if (count == 0)
              return;
  
          checkBounds(memoryOffset, memoryOffset + count);
          unsafe.copyMemory(buffer, BYTE_ARRAY_BASE_OFFSET + bufferOffset, null, peer + memoryOffset, count);
      }
  
      public byte getByte(long offset)
      {
          checkBounds(offset, offset + 1);
          return LittleEndianMemoryUtil.getByte(peer + offset);
      }
  
      public long getLong(long offset)
      {
          checkBounds(offset, offset + 8);
          return LittleEndianMemoryUtil.getLong(peer + offset);
      }
  
      public int getInt(long offset)
      {
          checkBounds(offset, offset + 4);
          return LittleEndianMemoryUtil.getInt(peer + offset);
      }
  
      /**
       * Transfers count bytes from Memory starting at memoryOffset to buffer starting at bufferOffset
       *
       * @param memoryOffset start offset in the memory
       * @param buffer the data buffer
       * @param bufferOffset start offset of the buffer
       * @param count number of bytes to transfer
       */
      public void getBytes(long memoryOffset, byte[] buffer, int bufferOffset, int count)
      {
          if (buffer == null)
              throw new NullPointerException();
          else if (bufferOffset < 0 || count < 0 || count > buffer.length - bufferOffset)
              throw new IndexOutOfBoundsException();
          else if (count == 0)
              return;
  
          checkBounds(memoryOffset, memoryOffset + count);
          FastByteOperations.UnsafeOperations.copy(null, peer + memoryOffset, buffer, bufferOffset, count);
      }
  
      @Inline
      protected void checkBounds(long start, long end)
      {
          assert peer != 0 : "Memory was freed";
          assert start >= 0 && end <= size && start <= end : "Illegal bounds [" + start + ".." + end + "); size: " + size;
      }
  
      public void put(long trgOffset, Memory memory, long srcOffset, long size)
      {
          checkBounds(trgOffset, trgOffset + size);
          memory.checkBounds(srcOffset, srcOffset + size);
          unsafe.copyMemory(memory.peer + srcOffset, peer + trgOffset, size);
      }
  
      public Memory copy(long newSize)
      {
          Memory copy = Memory.allocate(newSize);
          copy.put(0, this, 0, Math.min(size(), newSize));
          return copy;
      }
  
      public void free()
      {
          if (peer != 0) MemoryUtil.free(peer);
          else assert size == 0;
          peer = 0;
      }
  
      public void close()
      {
          free();
      }
  
      public long size()
      {
          assert peer != 0;
          return size;
      }
  
      @Override
      public boolean equals(Object o)
      {
          if (this == o)
              return true;
          if (!(o instanceof Memory))
              return false;
          Memory b = (Memory) o;
          if (peer == b.peer && size == b.size)
              return true;
          return false;
      }
  
      public ByteBuffer[] asByteBuffers(long offset, long length)
      {
          checkBounds(offset, offset + length);
          if (size() == 0)
              return NO_BYTE_BUFFERS;
  
          ByteBuffer[] result = new ByteBuffer[(int) (length / Integer.MAX_VALUE) + 1];
          int size = (int) (size() / result.length);
          for (int i = 0 ; i < result.length - 1 ; i++)
          {
              result[i] = LittleEndianMemoryUtil.getByteBuffer(peer + offset, size);
              offset += size;
              length -= size;
          }
          result[result.length - 1] = LittleEndianMemoryUtil.getByteBuffer(peer + offset, (int) length);
          return result;
      }
  
      public ByteBuffer asByteBuffer(long offset, int length)
      {
          checkBounds(offset, offset + length);
          return LittleEndianMemoryUtil.getByteBuffer(peer + offset, length);
      }
  
      // MUST provide a buffer created via MemoryUtil.getHollowDirectByteBuffer()
      public void setByteBuffer(ByteBuffer buffer, long offset, int length)
      {
          checkBounds(offset, offset + length);
          MemoryUtil.setDirectByteBuffer(buffer, peer + offset, length);
      }
  
      public String toString()
      {
          return toString(peer, size);
      }
  
      protected static String toString(long peer, long size)
      {
          return String.format("Memory@[%x..%x)", peer, peer + size);
      }
  }
  