/*
 *
 *  *  Copyright 2015 OrientDB LTD (info(at)orientdb.com)
 *  *
 *  *  Licensed 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.
 *  *
 *  * For more information: http://www.orientdb.com
 *
 */
package com.orientechnologies.common.directmemory;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.config.OContextConfiguration;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;

import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Object of this class works at the same time as factory for DirectByteBuffer objects and pool for
 * DirectByteBuffer objects which were used and now are free to be reused by other parts of the code.
 * All DirectByteBuffer objects have the same size which is specified in objects constructor as "page size".
 *
 * @see ODirectMemoryAllocator
 */
public final class OByteBufferPool implements OByteBufferPoolMXBean {
  /**
   * Whether we should track memory leaks during application execution
   */
  private static final boolean TRACK = OGlobalConfiguration.DIRECT_MEMORY_TRACK_MODE.getValueAsBoolean();

  /**
   * Holder for singleton instance. We use {@link AtomicReference} instead of static constructor to avoid throwing of exceptions
   * in static initializers.
   */
  private static final AtomicReference<OByteBufferPool> INSTANCE_HOLDER = new AtomicReference<>();

  /**
   * Limit of direct memory pointers are hold inside of the pool
   */
  private final int poolSize;

  /**
   * @return Singleton instance
   */
  public static OByteBufferPool instance(OContextConfiguration contextConfiguration) {
    final OByteBufferPool instance = INSTANCE_HOLDER.get();
    if (instance != null) {
      return instance;
    }

    int bufferSize;
    if (contextConfiguration != null) {
      bufferSize = contextConfiguration.getValueAsInteger(OGlobalConfiguration.DISK_CACHE_PAGE_SIZE);
    } else {
      bufferSize = OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger();
    }

    final OByteBufferPool newInstance = new OByteBufferPool(bufferSize * 1024);
    if (INSTANCE_HOLDER.compareAndSet(null, newInstance)) {
      return newInstance;
    }

    return INSTANCE_HOLDER.get();
  }

  /**
   * Size of single page in bytes.
   */
  private final int pageSize;

  /**
   * {@link ByteBuffer}s can not be extended, so to keep mapping between pointers and buffers we use concurrent hash map.
   */
  private final ConcurrentHashMap<OPointer, PointerTracker> pointerMapping = new ConcurrentHashMap<>();

  /**
   * Pool of already allocated pages.
   */
  private final ConcurrentLinkedQueue<OPointer> pointersPool = new ConcurrentLinkedQueue<>();

  /**
   * Size of the pool of pages is kept in separate counter because it is slow to ask pool itself and count all links in the pool.
   */
  private final AtomicInteger pointersPoolSize = new AtomicInteger();

  /**
   * Direct memory allocator.
   */
  private final ODirectMemoryAllocator allocator;

  /**
   * @param pageSize Size of single page (instance of DirectByteBuffer) returned by pool.
   */
  public OByteBufferPool(int pageSize) {
    this.pageSize = pageSize;
    this.allocator = ODirectMemoryAllocator.instance();
    this.poolSize = OGlobalConfiguration.DIRECT_MEMORY_POOL_LIMIT.getValueAsInteger();
  }

  /**
   * @param allocator Direct memory allocator to use.
   * @param pageSize  Size of single page (instance of DirectByteBuffer) returned by pool.
   * @param poolSize  Size of the page pool
   */
  public OByteBufferPool(int pageSize, ODirectMemoryAllocator allocator, int poolSize) {
    this.pageSize = pageSize;
    this.allocator = allocator;
    this.poolSize = poolSize;
  }

  /**
   * Acquires direct memory buffer with native byte order.
   * If there is free (already released) direct memory page we reuse it, otherwise new
   * memory chunk is allocated from direct memory.
   *
   * @param clear Whether returned buffer should be filled with zeros before return.
   *
   * @return Direct memory buffer instance.
   */
  public final OPointer acquireDirect(boolean clear) {
    OPointer pointer;

    pointer = pointersPool.poll();

    if (pointer != null) {
      pointersPoolSize.decrementAndGet();
    } else {
      pointer = allocator.allocate(pageSize, -1);
    }

    if (clear) {
      pointer.clear();
    }

    final ByteBuffer buffer = pointer.getNativeByteBuffer();
    buffer.position(0);

    if (TRACK) {
      pointerMapping.put(pointer, generatePointer());
    }

    return pointer;
  }

  /**
   * Put buffer which is not used any more back to the pool or frees direct memory if pool is full.
   *
   * @param pointer Not used instance of buffer.
   *
   * @see OGlobalConfiguration#DIRECT_MEMORY_POOL_LIMIT
   */
  public final void release(OPointer pointer) {
    if (TRACK) {
      pointerMapping.remove(pointer);
    }

    long poolSize = pointersPoolSize.incrementAndGet();
    if (poolSize > this.poolSize) {
      pointersPoolSize.decrementAndGet();
      allocator.deallocate(pointer);
    } else {
      pointersPool.add(pointer);
    }
  }

  /**
   * @inheritDoc
   */
  @Override
  public final int getPoolSize() {
    return pointersPoolSize.get();
  }

  /**
   * Checks whether there are not released buffers in the pool
   */
  public void checkMemoryLeaks() {
    boolean detected = false;
    if (TRACK) {
      for (Map.Entry<OPointer, PointerTracker> entry : pointerMapping.entrySet()) {
        OLogManager.instance()
            .errorNoDb(this, "DIRECT-TRACK: unreleased direct memory pointer `%X` detected.", entry.getValue().allocation,
                System.identityHashCode(entry.getKey()));
        detected = true;
      }
    }

    assert !detected;
  }

  /**
   * Clears pool and dealocates memory.
   */
  public void clear() {
    for (OPointer pointer : pointersPool) {
      allocator.deallocate(pointer);
    }

    pointersPool.clear();
    pointersPoolSize.set(0);

    for (OPointer pointer : pointerMapping.keySet()) {
      allocator.deallocate(pointer);
    }

    pointerMapping.clear();
  }

  /**
   * Holder which contains if memory tracking is enabled stack trace for the first allocation.
   */
  private static final class PointerTracker {
    private final Exception allocation;

    PointerTracker(Exception allocation) {
      this.allocation = allocation;
    }
  }

  private PointerTracker generatePointer() {
    return new PointerTracker(new Exception());
  }
}


/*
  *
  *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
  *  *
  *  *  Licensed 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.
  *  *
  *  * For more information: http://orientdb.com
  *
  */
package com.orientechnologies.orient.core.serialization;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Static helper class to transform any kind of basic data in bytes and vice versa.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OBinaryProtocol {

  public static final int SIZE_BYTE  = 1;
  public static final int SIZE_CHAR  = 2;
  public static final int SIZE_SHORT = 2;
  public static final int SIZE_INT   = 4;
  public static final int SIZE_LONG  = 8;

  public static byte[] char2bytes(final char value, final byte[] b, final int iBeginOffset) {
    b[iBeginOffset] = (byte) ((value >>> 8) & 0xFF);
    b[iBeginOffset + 1] = (byte) ((value >>> 0) & 0xFF);
    return b;
  }

  public static int long2bytes(final long value, final OutputStream iStream) throws IOException {
    final int beginOffset = iStream instanceof OMemoryStream ? ((OMemoryStream) iStream).getPosition() : -1;

    iStream.write((int) (value >>> 56) & 0xFF);
    iStream.write((int) (value >>> 48) & 0xFF);
    iStream.write((int) (value >>> 40) & 0xFF);
    iStream.write((int) (value >>> 32) & 0xFF);
    iStream.write((int) (value >>> 24) & 0xFF);
    iStream.write((int) (value >>> 16) & 0xFF);
    iStream.write((int) (value >>> 8) & 0xFF);
    iStream.write((int) (value >>> 0) & 0xFF);

    return beginOffset;
  }

  public static byte[] long2bytes(final long value) {
    return OBinaryProtocol.long2bytes(value, new byte[8], 0);
  }

  public static byte[] long2bytes(final long value, final byte[] b, final int iBeginOffset) {
    b[iBeginOffset] = (byte) ((value >>> 56) & 0xFF);
    b[iBeginOffset + 1] = (byte) ((value >>> 48) & 0xFF);
    b[iBeginOffset + 2] = (byte) ((value >>> 40) & 0xFF);
    b[iBeginOffset + 3] = (byte) ((value >>> 32) & 0xFF);
    b[iBeginOffset + 4] = (byte) ((value >>> 24) & 0xFF);
    b[iBeginOffset + 5] = (byte) ((value >>> 16) & 0xFF);
    b[iBeginOffset + 6] = (byte) ((value >>> 8) & 0xFF);
    b[iBeginOffset + 7] = (byte) ((value >>> 0) & 0xFF);
    return b;
  }

  public static int int2bytes(final int value, final OutputStream iStream) throws IOException {
    final int beginOffset = iStream instanceof OMemoryStream ? ((OMemoryStream) iStream).getPosition() : -1;

    iStream.write((value >>> 24) & 0xFF);
    iStream.write((value >>> 16) & 0xFF);
    iStream.write((value >>> 8) & 0xFF);
    iStream.write((value >>> 0) & 0xFF);

    return beginOffset;
  }

  public static byte[] int2bytes(final int value) {
    return OBinaryProtocol.int2bytes(value, new byte[4], 0);
  }

  public static byte[] int2bytes(final int value, final byte[] b, final int iBeginOffset) {
    b[iBeginOffset] = (byte) ((value >>> 24) & 0xFF);
    b[iBeginOffset + 1] = (byte) ((value >>> 16) & 0xFF);
    b[iBeginOffset + 2] = (byte) ((value >>> 8) & 0xFF);
    b[iBeginOffset + 3] = (byte) ((value >>> 0) & 0xFF);
    return b;
  }

  public static int short2bytes(final short value, final OutputStream iStream) throws IOException {
    final int beginOffset = iStream instanceof OMemoryStream ? ((OMemoryStream) iStream).getPosition() : -1;
    iStream.write((value >>> 8) & 0xFF);
    iStream.write((value >>> 0) & 0xFF);
    return beginOffset;
  }

  public static byte[] short2bytes(final short value) {
    return OBinaryProtocol.short2bytes(value, new byte[2], 0);
  }

  public static byte[] short2bytes(final short value, final byte[] b, final int iBeginOffset) {
    b[iBeginOffset] = (byte) ((value >>> 8) & 0xFF);
    b[iBeginOffset + 1] = (byte) ((value >>> 0) & 0xFF);
    return b;
  }

  public static long bytes2long(final byte[] b) {
    return OBinaryProtocol.bytes2long(b, 0);
  }

  public static long bytes2long(final InputStream iStream) throws IOException {
    return ((long) (0xff & iStream.read()) << 56 | (long) (0xff & iStream.read()) << 48 | (long) (0xff & iStream.read()) << 40
        | (long) (0xff & iStream.read()) << 32 | (long) (0xff & iStream.read()) << 24 | (0xff & iStream.read()) << 16
        | (0xff & iStream.read()) << 8 | (0xff & iStream.read()));
  }

  public static long bytes2long(final byte[] b, final int offset) {
    return ((0xff & b[offset + 7]) | (0xff & b[offset + 6]) << 8 | (0xff & b[offset + 5]) << 16
        | (long) (0xff & b[offset + 4]) << 24 | (long) (0xff & b[offset + 3]) << 32 | (long) (0xff & b[offset + 2]) << 40
        | (long) (0xff & b[offset + 1]) << 48 | (long) (0xff & b[offset]) << 56);
  }

  /**
   * Convert the byte array to an int.
   *
   * @param b The byte array
   * @return The integer
   */
  public static int bytes2int(final byte[] b) {
    return bytes2int(b, 0);
  }

  public static int bytes2int(final InputStream iStream) throws IOException {
    return ((0xff & iStream.read()) << 24 | (0xff & iStream.read()) << 16 | (0xff & iStream.read()) << 8 | (0xff & iStream.read()));
  }

  /**
   * Convert the byte array to an int starting from the given offset.
   *
   * @param b      The byte array
   * @param offset The array offset
   * @return The integer
   */
  public static int bytes2int(final byte[] b, final int offset) {
    return (b[offset]) << 24 | (0xff & b[offset + 1]) << 16 | (0xff & b[offset + 2]) << 8 | ((0xff & b[offset + 3]));
  }

  public static int bytes2short(final InputStream iStream) throws IOException {
    return (short) ((iStream.read() << 8) | (iStream.read() & 0xff));
  }

  public static short bytes2short(final byte[] b, final int offset) {
    return (short) ((b[offset] << 8) | (b[offset + 1] & 0xff));
  }

  public static char bytes2char(final byte[] b, final int offset) {
    return (char) ((b[offset] << 8) + (b[offset + 1] & 0xff));
  }

}


/*
  *
  *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
  *  *
  *  *  Licensed 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.
  *  *
  *  * For more information: http://orientdb.com
  *
  */

package com.orientechnologies.orient.object.jpa.parsing;

import static com.orientechnologies.orient.object.jpa.parsing.PersistenceXml.ATTR_SCHEMA_VERSION;
import static com.orientechnologies.orient.object.jpa.parsing.PersistenceXml.TAG_PERSISTENCE;
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.EnumSet;

import javax.persistence.PersistenceException;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Simple handler for persistence.xml files.
 */
public final class PersistenceXmlUtil {
  /**
   * URI for the JPA persistence namespace
   */
  public static final String            PERSISTENCE_NS_URI        = "http://java.sun.com/xml/ns/persistence";

  private final static SchemaFactory    schemaFactory             = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
  private final static SAXParserFactory parserFactory             = SAXParserFactory.newInstance();
  static {
    parserFactory.setNamespaceAware(true);
  }
  /** The persistence xml root */
  public static final String            PERSISTENCE_XML_ROOT      = "META-INF/";
  public static final String            PERSISTENCE_XML_BASE_NAME = "persistence.xml";
  /** The persistence XSD location */
  public static final String            PERSISTENCE_XSD_DIR       = PERSISTENCE_XML_ROOT + "persistence/";
  /** The persistence XML location */
  public static final String            PERSISTENCE_XML           = PERSISTENCE_XML_ROOT + PERSISTENCE_XML_BASE_NAME;

  private PersistenceXmlUtil() {
  }

  /**
   * Parse the persistence.xml files referenced by the URLs in the collection
   *
   * @param persistenceXml
   * @return A collection of parsed persistence units, or null if nothing has been found
   */
  public static PersistenceUnitInfo findPersistenceUnit(String unitName, Collection<? extends PersistenceUnitInfo> units) {
    if (units == null || unitName == null) {
      return null;
    }

    for (PersistenceUnitInfo unit : units) {
      if (unitName.equals(unit.getPersistenceUnitName())) {
        return unit;
      }
    }
    return null;
  }

  /**
   * Parse the persistence.xml files referenced by the URLs in the collection
   *
   * @param persistenceXml
   * @return A collection of parsed persistence units.
   * @throws IOException
   * @throws SAXException
   * @throws ParserConfigurationException
   */
  public static Collection<? extends PersistenceUnitInfo> parse(URL persistenceXml) {

    InputStream is = null;
    try {
      // Buffer the InputStream so we can mark it, though we'll be in
      // trouble if we have to read more than 8192 characters before finding
      // the schema!
      is = new BufferedInputStream(persistenceXml.openStream());

      JPAVersion jpaVersion = getSchemaVersion(is);
      Schema schema = getSchema(jpaVersion);

      if (schema == null) {
        throw new PersistenceException("Schema is unknown");
      }

      // Get back to the beginning of the stream
      is = new BufferedInputStream(persistenceXml.openStream());

      parserFactory.setNamespaceAware(true);

      int endIndex = persistenceXml.getPath().length() - PERSISTENCE_XML_BASE_NAME.length();
      URL persistenceXmlRoot = new URL("file://" + persistenceXml.getFile().substring(0, endIndex));

      return getPersistenceUnits(is, persistenceXmlRoot, jpaVersion);
    } catch (Exception e) {
      throw new PersistenceException("Something goes wrong while parsing persistence.xml", e);
    } finally {
      if (is != null)
        try {
          is.close();
        } catch (IOException e) {
          // No logging necessary, just consume
        }
    }
  }

  public static Schema getSchema(JPAVersion version) throws SAXException {
    String schemaPath = PERSISTENCE_XSD_DIR + version.getFilename();
    InputStream inputStream = PersistenceXmlUtil.class.getClassLoader().getResourceAsStream(schemaPath);
    return schemaFactory.newSchema(new StreamSource(inputStream));
  }

  public static JPAVersion getSchemaVersion(InputStream is) throws ParserConfigurationException, SAXException, IOException {
    SchemaLocatingHandler schemaHandler = parse(is, new SchemaLocatingHandler());
    return JPAVersion.parse(schemaHandler.getVersion());
  }

  public static Collection<? extends PersistenceUnitInfo> getPersistenceUnits(InputStream is, URL xmlRoot, JPAVersion version)
      throws ParserConfigurationException, SAXException, IOException {
    JPAHandler handler = new JPAHandler(xmlRoot, version);
    return parse(is, handler).getPersistenceUnits();
  }

  /**
   * @param is
   *          - xml file to be validated
   * @param handler
   * @return handler for chained calls
   * @throws ParserConfigurationException
   * @throws SAXException
   * @throws IOException
   */
  protected static <T extends DefaultHandler> T parse(InputStream is, T handler) throws ParserConfigurationException, SAXException,
      IOException {
    try {
      SAXParser parser = parserFactory.newSAXParser();
      parser.parse(is, handler);
    } catch (StopSAXParser e) {
      // This is not really an exception, but a way to work out which
      // version of the persistence schema to use in validation
    }
    return handler;
  }

  /**
   * @param uri
   * @param element
   * @param attributes
   * @return XML Schema Version or null
   * @throws SAXException
   */
  public static String parseSchemaVersion(String uri, PersistenceXml element, Attributes attributes) throws SAXException {
    if (PERSISTENCE_NS_URI.equals(uri) && TAG_PERSISTENCE == element) {
      return attributes.getValue(ATTR_SCHEMA_VERSION.toString());
    }
    return null;
  }
}

/**
 * This parser provides a quick mechanism for determining the JPA schema level, and throws an StopSAXParser with the Schema to
 * validate with
 */
class SchemaLocatingHandler extends DefaultHandler {

  /** The value of the version attribute in the xml */
  private String schemaVersion = "";

  /**
   * Create a new SchemaLocatingHandler
   */
  public SchemaLocatingHandler() {
  }

  /**
   * Fist tag of 'persistence.xml' (<persistence>) have to have 'version' attribute
   *
   * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
   *      org.xml.sax.Attributes)
   */
  @Override
  public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
    PersistenceXml element = PersistenceXml.parse((localName == null || localName.isEmpty()) ? name : localName);
    schemaVersion = PersistenceXmlUtil.parseSchemaVersion(uri, element, attributes);
    // found, stop parsing
    if (schemaVersion != null) {
      throw new StopSAXParser();
    }

    // This should never occurs, however check if contain known tag other than TAG_PERSISTENCE
    if (TAG_PERSISTENCE != element && EnumSet.allOf(PersistenceXml.class).contains(element)) {
      throw new PersistenceException("Cannot find schema version attribute in <persistence> tag");
    }
  }

  /**
   * @return The version of the JPA schema used
   */
  public String getVersion() {
    return schemaVersion;
  }
}

/**
 * A convenience mechanism for finding the schema to validate with
 */
class StopSAXParser extends SAXException {
  /** This class is serializable */
  private static final long serialVersionUID = 6173561761817524327L;
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed 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.
 *  *
 *  * For more information: http://orientdb.com
 *
 */

package com.orientechnologies.orient.core.serialization.serializer.record.binary;

public class OVarIntSerializer {

  public static int write(BytesContainer bytes, long value) {
    value = signedToUnsigned(value);
    int pos = bytes.offset;
    writeUnsignedVarLong(value, bytes);
    return pos;

  }

  public static short readAsShort(final BytesContainer bytes) {
    return (short) readSignedVarLong(bytes);
  }

  public static long readAsLong(final BytesContainer bytes) {
    return readSignedVarLong(bytes);
  }

  public static int readAsInteger(final BytesContainer bytes) {
    return (int) readSignedVarLong(bytes);
  }

  public static byte readAsByte(final BytesContainer bytes) {
    return (byte) readSignedVarLong(bytes);
  }

  /**
   * Encodes a value using the variable-length encoding from  Google Protocol Buffers. It uses zig-zag encoding to
   * efficiently encode signed values.
   *
   * @param value value to encode
   *
   */
  private static long signedToUnsigned(long value) {
    return (value << 1) ^ (value >> 63);
  }

  /**
   * Encodes a value using the variable-length encoding from  Google Protocol Buffers. Zig-zag is not used, so
   * input must not be negative.
   *
   * @param value value to encode
   *
   * @return the number of bytes written
   */
  public static void writeUnsignedVarLong(long value, final BytesContainer bos) {
    int pos;
    while ((value & 0xFFFFFFFFFFFFFF80L) != 0L) {
      // out.writeByte(((int) value & 0x7F) | 0x80);
      pos = bos.alloc((short) 1);
      bos.bytes[pos] = (byte) (value & 0x7F | 0x80);
      value >>>= 7;
    }
    // out.writeByte((int) value & 0x7F);
    pos = bos.alloc((short) 1);
    bos.bytes[pos] = (byte) (value & 0x7F);
  }

  /**
   * @param bytes to read bytes from
   *
   * @return decode value
   *
   * @throws IllegalArgumentException if variable-length value does not terminate after 9 bytes have been read
   */
  public static long readSignedVarLong(final BytesContainer bytes) {
    final long raw = readUnsignedVarLong(bytes);
    // This undoes the trick in writeSignedVarLong()
    final long temp = (((raw << 63) >> 63) ^ raw) >> 1;
    // This extra step lets us deal with the largest signed values by
    // treating
    // negative results from read unsigned methods as like unsigned values
    // Must re-flip the top bit if the original read value had it set.
    return temp ^ (raw & (1L << 63));
  }

  /**
   * @param bytes to read bytes from
   *
   * @return decode value
   *
   * @throws IllegalArgumentException if variable-length value does not terminate after 9 bytes have been read
   */
  public static long readUnsignedVarLong(final BytesContainer bytes) {
    long value = 0L;
    int i = 0;
    long b;
    while (((b = bytes.bytes[bytes.offset++]) & 0x80L) != 0) {
      value |= (b & 0x7F) < 63)
        throw new IllegalArgumentException("Variable length quantity is too long (must be <= 63)");
    }
    return value | (b << i);
  }

}