/*
 * Copyright 2014 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack;

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

import com.twitter.hpack.HpackUtil.IndexType;

import static com.twitter.hpack.HeaderField.HEADER_ENTRY_OVERHEAD;

public final class Decoder {

  private static final IOException DECOMPRESSION_EXCEPTION =
      new IOException("decompression failure");
  private static final IOException ILLEGAL_INDEX_VALUE =
      new IOException("illegal index value");
  private static final IOException INVALID_MAX_DYNAMIC_TABLE_SIZE =
      new IOException("invalid max dynamic table size");
  private static final IOException MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED =
      new IOException("max dynamic table size change required");

  private static final byte[] EMPTY = {};

  private final DynamicTable dynamicTable;

  private int maxHeaderSize;
  private int maxDynamicTableSize;
  private int encoderMaxDynamicTableSize;
  private boolean maxDynamicTableSizeChangeRequired;

  private long headerSize;
  private State state;
  private IndexType indexType;
  private int index;
  private boolean huffmanEncoded;
  private int skipLength;
  private int nameLength;
  private int valueLength;
  private byte[] name;

  private enum State {
    READ_HEADER_REPRESENTATION,
    READ_MAX_DYNAMIC_TABLE_SIZE,
    READ_INDEXED_HEADER,
    READ_INDEXED_HEADER_NAME,
    READ_LITERAL_HEADER_NAME_LENGTH_PREFIX,
    READ_LITERAL_HEADER_NAME_LENGTH,
    READ_LITERAL_HEADER_NAME,
    SKIP_LITERAL_HEADER_NAME,
    READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX,
    READ_LITERAL_HEADER_VALUE_LENGTH,
    READ_LITERAL_HEADER_VALUE,
    SKIP_LITERAL_HEADER_VALUE
  }

  /**
   * Creates a new decoder.
   */
  public Decoder(int maxHeaderSize, int maxHeaderTableSize) {
    dynamicTable = new DynamicTable(maxHeaderTableSize);
    this.maxHeaderSize = maxHeaderSize;
    maxDynamicTableSize = maxHeaderTableSize;
    encoderMaxDynamicTableSize = maxHeaderTableSize;
    maxDynamicTableSizeChangeRequired = false;
    reset();
  }

  private void reset() {
    headerSize = 0;
    state = State.READ_HEADER_REPRESENTATION;
    indexType = IndexType.NONE;
  }

  /**
   * Decode the header block into header fields.
   */
  public void decode(InputStream in, HeaderListener headerListener) throws IOException {
    while (in.available() > 0) {
      switch(state) {
      case READ_HEADER_REPRESENTATION:
        byte b = (byte) in.read();
        if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
          // Encoder MUST signal maximum dynamic table size change
          throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
        }
        if (b < 0) {
          // Indexed Header Field
          index = b & 0x7F;
          if (index == 0) {
            throw ILLEGAL_INDEX_VALUE;
          } else if (index == 0x7F) {
            state = State.READ_INDEXED_HEADER;
          } else {
            indexHeader(index, headerListener);
          }
        } else if ((b & 0x40) == 0x40) {
          // Literal Header Field with Incremental Indexing
          indexType = IndexType.INCREMENTAL;
          index = b & 0x3F;
          if (index == 0) {
            state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
          } else if (index == 0x3F) {
            state = State.READ_INDEXED_HEADER_NAME;
          } else {
            // Index was stored as the prefix
            readName(index);
            state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
          }
        } else if ((b & 0x20) == 0x20) {
          // Dynamic Table Size Update
          index = b & 0x1F;
          if (index == 0x1F) {
            state = State.READ_MAX_DYNAMIC_TABLE_SIZE;
          } else {
            setDynamicTableSize(index);
            state = State.READ_HEADER_REPRESENTATION;
          }
        } else {
          // Literal Header Field without Indexing / never Indexed
          indexType = ((b & 0x10) == 0x10) ? IndexType.NEVER : IndexType.NONE;
          index = b & 0x0F;
          if (index == 0) {
            state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
          } else if (index == 0x0F) {
            state = State.READ_INDEXED_HEADER_NAME;
          } else {
            // Index was stored as the prefix
            readName(index);
            state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
          }
        }
        break;

      case READ_MAX_DYNAMIC_TABLE_SIZE:
        int maxSize = decodeULE128(in);
        if (maxSize == -1) {
          return;
        }

        // Check for numerical overflow
        if (maxSize > Integer.MAX_VALUE - index) {
          throw DECOMPRESSION_EXCEPTION;
        }

        setDynamicTableSize(index + maxSize);
        state = State.READ_HEADER_REPRESENTATION;
        break;

      case READ_INDEXED_HEADER:
        int headerIndex = decodeULE128(in);
        if (headerIndex == -1) {
          return;
        }

        // Check for numerical overflow
        if (headerIndex > Integer.MAX_VALUE - index) {
          throw DECOMPRESSION_EXCEPTION;
        }

        indexHeader(index + headerIndex, headerListener);
        state = State.READ_HEADER_REPRESENTATION;
        break;

      case READ_INDEXED_HEADER_NAME:
        // Header Name matches an entry in the Header Table
        int nameIndex = decodeULE128(in);
        if (nameIndex == -1) {
          return;
        }

        // Check for numerical overflow
        if (nameIndex > Integer.MAX_VALUE - index) {
          throw DECOMPRESSION_EXCEPTION;
        }

        readName(index + nameIndex);
        state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
        break;

      case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:
        b = (byte) in.read();
        huffmanEncoded = (b & 0x80) == 0x80;
        index = b & 0x7F;
        if (index == 0x7f) {
          state = State.READ_LITERAL_HEADER_NAME_LENGTH;
        } else {
          nameLength = index;

          // Disallow empty names -- they cannot be represented in HTTP/1.x
          if (nameLength == 0) {
            throw DECOMPRESSION_EXCEPTION;
          }

          // Check name length against max header size
          if (exceedsMaxHeaderSize(nameLength)) {

            if (indexType == IndexType.NONE) {
              // Name is unused so skip bytes
              name = EMPTY;
              skipLength = nameLength;
              state = State.SKIP_LITERAL_HEADER_NAME;
              break;
            }

            // Check name length against max dynamic table size
            if (nameLength + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
              dynamicTable.clear();
              name = EMPTY;
              skipLength = nameLength;
              state = State.SKIP_LITERAL_HEADER_NAME;
              break;
            }
          }
          state = State.READ_LITERAL_HEADER_NAME;
        }
        break;

      case READ_LITERAL_HEADER_NAME_LENGTH:
        // Header Name is a Literal String
        nameLength = decodeULE128(in);
        if (nameLength == -1) {
          return;
        }

        // Check for numerical overflow
        if (nameLength > Integer.MAX_VALUE - index) {
          throw DECOMPRESSION_EXCEPTION;
        }
        nameLength += index;

        // Check name length against max header size
        if (exceedsMaxHeaderSize(nameLength)) {
          if (indexType == IndexType.NONE) {
            // Name is unused so skip bytes
            name = EMPTY;
            skipLength = nameLength;
            state = State.SKIP_LITERAL_HEADER_NAME;
            break;
          }

          // Check name length against max dynamic table size
          if (nameLength + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
            dynamicTable.clear();
            name = EMPTY;
            skipLength = nameLength;
            state = State.SKIP_LITERAL_HEADER_NAME;
            break;
          }
        }
        state = State.READ_LITERAL_HEADER_NAME;
        break;

      case READ_LITERAL_HEADER_NAME:
        // Wait until entire name is readable
        if (in.available() < nameLength) {
          return;
        }

        name = readStringLiteral(in, nameLength);

        state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
        break;

      case SKIP_LITERAL_HEADER_NAME:
        skipLength -= in.skip(skipLength);

        if (skipLength == 0) {
          state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
        }
        break;

      case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
        b = (byte) in.read();
        huffmanEncoded = (b & 0x80) == 0x80;
        index = b & 0x7F;
        if (index == 0x7f) {
          state = State.READ_LITERAL_HEADER_VALUE_LENGTH;
        } else {
          valueLength = index;

          // Check new header size against max header size
          long newHeaderSize = (long) nameLength + (long) valueLength;
          if (exceedsMaxHeaderSize(newHeaderSize)) {
            // truncation will be reported during endHeaderBlock
            headerSize = maxHeaderSize + 1;

            if (indexType == IndexType.NONE) {
              // Value is unused so skip bytes
              state = State.SKIP_LITERAL_HEADER_VALUE;
              break;
            }

            // Check new header size against max dynamic table size
            if (newHeaderSize + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
              dynamicTable.clear();
              state = State.SKIP_LITERAL_HEADER_VALUE;
              break;
            }
          }

          if (valueLength == 0) {
            insertHeader(headerListener, name, EMPTY, indexType);
            state = State.READ_HEADER_REPRESENTATION;
          } else {
            state = State.READ_LITERAL_HEADER_VALUE;
          }
        }

        break;

      case READ_LITERAL_HEADER_VALUE_LENGTH:
        // Header Value is a Literal String
        valueLength = decodeULE128(in);
        if (valueLength == -1) {
          return;
        }

        // Check for numerical overflow
        if (valueLength > Integer.MAX_VALUE - index) {
          throw DECOMPRESSION_EXCEPTION;
        }
        valueLength += index;

        // Check new header size against max header size
        long newHeaderSize = (long) nameLength + (long) valueLength;
        if (newHeaderSize + headerSize > maxHeaderSize) {
          // truncation will be reported during endHeaderBlock
          headerSize = maxHeaderSize + 1;

          if (indexType == IndexType.NONE) {
            // Value is unused so skip bytes
            state = State.SKIP_LITERAL_HEADER_VALUE;
            break;
          }

          // Check new header size against max dynamic table size
          if (newHeaderSize + HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
            dynamicTable.clear();
            state = State.SKIP_LITERAL_HEADER_VALUE;
            break;
          }
        }
        state = State.READ_LITERAL_HEADER_VALUE;
        break;

      case READ_LITERAL_HEADER_VALUE:
        // Wait until entire value is readable
        if (in.available() < valueLength) {
          return;
        }

        byte[] value = readStringLiteral(in, valueLength);
        insertHeader(headerListener, name, value, indexType);
        state = State.READ_HEADER_REPRESENTATION;
        break;

      case SKIP_LITERAL_HEADER_VALUE:
        valueLength -= in.skip(valueLength);

        if (valueLength == 0) {
          state = State.READ_HEADER_REPRESENTATION;
        }
        break;

      default:
        throw new IllegalStateException("should not reach here");
      }
    }
  }

  /**
   * End the current header block. Returns if the header field has been truncated.
   * This must be called after the header block has been completely decoded.
   */
  public boolean endHeaderBlock() {
    boolean truncated = headerSize > maxHeaderSize;
    reset();
    return truncated;
  }

  /**
   * Set the maximum table size.
   * If this is below the maximum size of the dynamic table used by the encoder,
   * the beginning of the next header block MUST signal this change.
   */
  public void setMaxHeaderTableSize(int maxHeaderTableSize) {
    maxDynamicTableSize = maxHeaderTableSize;
    if (maxDynamicTableSize < encoderMaxDynamicTableSize) {
      // decoder requires less space than encoder
      // encoder MUST signal this change
      maxDynamicTableSizeChangeRequired = true;
      dynamicTable.setCapacity(maxDynamicTableSize);
    }
  }

  /**
   * Return the maximum table size.
   * This is the maximum size allowed by both the encoder and the decoder.
   */
  public int getMaxHeaderTableSize() {
    return dynamicTable.capacity();
  }

  /**
   * Return the number of header fields in the dynamic table.
   * Exposed for testing.
   */
  int length() {
    return dynamicTable.length();
  }

  /**
   * Return the size of the dynamic table.
   * Exposed for testing.
   */
  int size() {
    return dynamicTable.size();
  }

  /**
   * Return the header field at the given index.
   * Exposed for testing.
   */
  HeaderField getHeaderField(int index) {
    return dynamicTable.getEntry(index + 1);
  }

  private void setDynamicTableSize(int dynamicTableSize) throws IOException {
    if (dynamicTableSize > maxDynamicTableSize) {
      throw INVALID_MAX_DYNAMIC_TABLE_SIZE;
    }
    encoderMaxDynamicTableSize = dynamicTableSize;
    maxDynamicTableSizeChangeRequired = false;
    dynamicTable.setCapacity(dynamicTableSize);
  }

  private void readName(int index) throws IOException {
    if (index <= StaticTable.length) {
      HeaderField headerField = StaticTable.getEntry(index);
      name = headerField.name;
    } else if (index - StaticTable.length <= dynamicTable.length()) {
      HeaderField headerField = dynamicTable.getEntry(index - StaticTable.length);
      name = headerField.name;
    } else {
      throw ILLEGAL_INDEX_VALUE;
    }
  }

  private void indexHeader(int index, HeaderListener headerListener) throws IOException {
    if (index <= StaticTable.length) {
      HeaderField headerField = StaticTable.getEntry(index);
      addHeader(headerListener, headerField.name, headerField.value, false);
    } else if (index - StaticTable.length <= dynamicTable.length()) {
      HeaderField headerField = dynamicTable.getEntry(index - StaticTable.length);
      addHeader(headerListener, headerField.name, headerField.value, false);
    } else {
      throw ILLEGAL_INDEX_VALUE;
    }
  }

  private void insertHeader(HeaderListener headerListener, byte[] name, byte[] value, IndexType indexType) {
    addHeader(headerListener, name, value, indexType == IndexType.NEVER);

    switch (indexType) {
      case NONE:
      case NEVER:
        break;

      case INCREMENTAL:
        dynamicTable.add(new HeaderField(name, value));
        break;

      default:
        throw new IllegalStateException("should not reach here");
    }
  }

  private void addHeader(HeaderListener headerListener, byte[] name, byte[] value, boolean sensitive) {
    if (name.length == 0) {
      throw new AssertionError("name is empty");
    }
    long newSize = headerSize + name.length + value.length;
    if (newSize <= maxHeaderSize) {
      headerListener.addHeader(name, value, sensitive);
      headerSize = (int) newSize;
    } else {
      // truncation will be reported during endHeaderBlock
      headerSize = maxHeaderSize + 1;
    }
  }

  private boolean exceedsMaxHeaderSize(long size) {
    // Check new header size against max header size
    if (size + headerSize <= maxHeaderSize) {
      return false;
    }

    // truncation will be reported during endHeaderBlock
    headerSize = maxHeaderSize + 1;
    return true;
  }

  private byte[] readStringLiteral(InputStream in, int length) throws IOException {
    byte[] buf = new byte[length];
    if (in.read(buf) != length) {
      throw DECOMPRESSION_EXCEPTION;
    }

    if (huffmanEncoded) {
      return Huffman.DECODER.decode(buf);
    } else {
      return buf;
    }
  }

  // Unsigned Little Endian Base 128 Variable-Length Integer Encoding
  private static int decodeULE128(InputStream in) throws IOException {
    in.mark(5);
    int result = 0;
    int shift = 0;
    while (shift < 32) {
      if (in.available() == 0) {
        // Buffer does not contain entire integer,
        // reset reader index and return -1.
        in.reset();
        return -1;
      }
      byte b = (byte) in.read();
      if (shift == 28 && (b & 0xF8) != 0) {
        break;
      }
      result |= (b & 0x7F) << shift;
      if ((b & 0x80) == 0) {
        return result;
      }
      shift += 7;
    }
    // Value exceeds Integer.MAX_VALUE
    in.reset();
    throw DECOMPRESSION_EXCEPTION;
  }
}


/*
 * Copyright 2014 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.twitter.hpack.HpackUtil.ISO_8859_1;

final class StaticTable {

  private static final String EMPTY = "";

  // Appendix A: Static Table
  // http://tools.ietf.org/html/rfc7541#appendix-A
  private static final List<HeaderField> STATIC_TABLE = Arrays.asList(
    /*  1 */ new HeaderField(":authority", EMPTY),
    /*  2 */ new HeaderField(":method", "GET"),
    /*  3 */ new HeaderField(":method", "POST"),
    /*  4 */ new HeaderField(":path", "/"),
    /*  5 */ new HeaderField(":path", "/index.html"),
    /*  6 */ new HeaderField(":scheme", "http"),
    /*  7 */ new HeaderField(":scheme", "https"),
    /*  8 */ new HeaderField(":status", "200"),
    /*  9 */ new HeaderField(":status", "204"),
    /* 10 */ new HeaderField(":status", "206"),
    /* 11 */ new HeaderField(":status", "304"),
    /* 12 */ new HeaderField(":status", "400"),
    /* 13 */ new HeaderField(":status", "404"),
    /* 14 */ new HeaderField(":status", "500"),
    /* 15 */ new HeaderField("accept-charset", EMPTY),
    /* 16 */ new HeaderField("accept-encoding", "gzip, deflate"),
    /* 17 */ new HeaderField("accept-language", EMPTY),
    /* 18 */ new HeaderField("accept-ranges", EMPTY),
    /* 19 */ new HeaderField("accept", EMPTY),
    /* 20 */ new HeaderField("access-control-allow-origin", EMPTY),
    /* 21 */ new HeaderField("age", EMPTY),
    /* 22 */ new HeaderField("allow", EMPTY),
    /* 23 */ new HeaderField("authorization", EMPTY),
    /* 24 */ new HeaderField("cache-control", EMPTY),
    /* 25 */ new HeaderField("content-disposition", EMPTY),
    /* 26 */ new HeaderField("content-encoding", EMPTY),
    /* 27 */ new HeaderField("content-language", EMPTY),
    /* 28 */ new HeaderField("content-length", EMPTY),
    /* 29 */ new HeaderField("content-location", EMPTY),
    /* 30 */ new HeaderField("content-range", EMPTY),
    /* 31 */ new HeaderField("content-type", EMPTY),
    /* 32 */ new HeaderField("cookie", EMPTY),
    /* 33 */ new HeaderField("date", EMPTY),
    /* 34 */ new HeaderField("etag", EMPTY),
    /* 35 */ new HeaderField("expect", EMPTY),
    /* 36 */ new HeaderField("expires", EMPTY),
    /* 37 */ new HeaderField("from", EMPTY),
    /* 38 */ new HeaderField("host", EMPTY),
    /* 39 */ new HeaderField("if-match", EMPTY),
    /* 40 */ new HeaderField("if-modified-since", EMPTY),
    /* 41 */ new HeaderField("if-none-match", EMPTY),
    /* 42 */ new HeaderField("if-range", EMPTY),
    /* 43 */ new HeaderField("if-unmodified-since", EMPTY),
    /* 44 */ new HeaderField("last-modified", EMPTY),
    /* 45 */ new HeaderField("link", EMPTY),
    /* 46 */ new HeaderField("location", EMPTY),
    /* 47 */ new HeaderField("max-forwards", EMPTY),
    /* 48 */ new HeaderField("proxy-authenticate", EMPTY),
    /* 49 */ new HeaderField("proxy-authorization", EMPTY),
    /* 50 */ new HeaderField("range", EMPTY),
    /* 51 */ new HeaderField("referer", EMPTY),
    /* 52 */ new HeaderField("refresh", EMPTY),
    /* 53 */ new HeaderField("retry-after", EMPTY),
    /* 54 */ new HeaderField("server", EMPTY),
    /* 55 */ new HeaderField("set-cookie", EMPTY),
    /* 56 */ new HeaderField("strict-transport-security", EMPTY),
    /* 57 */ new HeaderField("transfer-encoding", EMPTY),
    /* 58 */ new HeaderField("user-agent", EMPTY),
    /* 59 */ new HeaderField("vary", EMPTY),
    /* 60 */ new HeaderField("via", EMPTY),
    /* 61 */ new HeaderField("www-authenticate", EMPTY)
  );

  private static final Map<String, Integer> STATIC_INDEX_BY_NAME = createMap();

  /**
   * The number of header fields in the static table.
   */
  static final int length = STATIC_TABLE.size();

  /**
   * Return the header field at the given index value.
   */
  static HeaderField getEntry(int index) {
    return STATIC_TABLE.get(index - 1);
  }

  /**
   * Returns the lowest index value for the given header field name in the static table.
   * Returns -1 if the header field name is not in the static table.
   */
  static int getIndex(byte[] name) {
    String nameString = new String(name, 0, name.length, ISO_8859_1);
    Integer index = STATIC_INDEX_BY_NAME.get(nameString);
    if (index == null) {
      return -1;
    }
    return index;
  }

  /**
   * Returns the index value for the given header field in the static table.
   * Returns -1 if the header field is not in the static table.
   */
  static int getIndex(byte[] name, byte[] value) {
    int index = getIndex(name);
    if (index == -1) {
      return -1;
    }

    // Note this assumes all entries for a given header field are sequential.
    while (index <= length) {
      HeaderField entry = getEntry(index);
      if (!HpackUtil.equals(name, entry.name)) {
        break;
      }
      if (HpackUtil.equals(value, entry.value)) {
        return index;
      }
      index++;
    }

    return -1;
  }

  // create a map of header name to index value to allow quick lookup
  private static Map<String, Integer> createMap() {
    int length = STATIC_TABLE.size();
    HashMap<String, Integer> ret = new HashMap<String, Integer>(length);
    // Iterate through the static table in reverse order to
    // save the smallest index for a given name in the map.
    for (int index = length; index > 0; index--) {
      HeaderField entry = getEntry(index);
      String name = new String(entry.name, 0, entry.name.length, ISO_8859_1);
      ret.put(name, index);
    }
    return ret;
  }

  // singleton
  private StaticTable() {}
}


/*
 * Copyright 2014 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack;

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

final class HuffmanEncoder {

  private final int[] codes;
  private final byte[] lengths;

  /**
   * Creates a new Huffman encoder with the specified Huffman coding.
   * @param codes   the Huffman codes indexed by symbol
   * @param lengths the length of each Huffman code
   */
  HuffmanEncoder(int[] codes, byte[] lengths) {
    this.codes = codes;
    this.lengths = lengths;
  }

  /**
   * Compresses the input string literal using the Huffman coding.
   * @param  out  the output stream for the compressed data
   * @param  data the string literal to be Huffman encoded
   * @throws IOException if an I/O error occurs.
   * @see    com.twitter.hpack.HuffmanEncoder#encode(OutputStream, byte[], int, int)
   */
  public void encode(OutputStream out, byte[] data) throws IOException {
    encode(out, data, 0, data.length);
  }

  /**
   * Compresses the input string literal using the Huffman coding.
   * @param  out  the output stream for the compressed data
   * @param  data the string literal to be Huffman encoded
   * @param  off  the start offset in the data
   * @param  len  the number of bytes to encode
   * @throws IOException if an I/O error occurs. In particular,
   *         an IOException may be thrown if the
   *         output stream has been closed.
   */
  public void encode(OutputStream out, byte[] data, int off, int len) throws IOException {
    if (out == null) {
      throw new NullPointerException("out");
    } else if (data == null) {
      throw new NullPointerException("data");
    } else if (off < 0 || len < 0 || (off + len) < 0 || off > data.length || (off + len) > data.length) {
      throw new IndexOutOfBoundsException();
    } else if (len == 0) {
      return;
    }

    long current = 0;
    int n = 0;

    for (int i = 0; i < len; i++) {
      int b = data[off + i] & 0xFF;
      int code = codes[b];
      int nbits = lengths[b];

      current <<= nbits;
      current |= code;
      n += nbits;

      while (n >= 8) {
        n -= 8;
        out.write(((int)(current >> n)));
      }
    }

    if (n > 0) {
      current <<= (8 - n);
      current |= (0xFF >>> n); // this should be EOS symbol
      out.write((int)current);
    }
  }

  /**
   * Returns the number of bytes required to Huffman encode the input string literal.
   * @param  data the string literal to be Huffman encoded
   * @return the number of bytes required to Huffman encode data
   */
  public int getEncodedLength(byte[] data) {
    if (data == null) {
      throw new NullPointerException("data");
    }
    long len = 0;
    for (byte b : data) {
      len += lengths[b & 0xFF];
    }
    return (int)((len + 7) >> 3);
  }
}


/*
 * Copyright 2015 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack.microbench;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Helper class representing a single header entry. Used by the benchmarks.
 */
class Header {
    private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";

    final byte[] name;
    final byte[] value;

    Header(byte[] name, byte[] value) {
        this.name = name;
        this.value = value;
    }

    /**
     * Creates a number of random headers with the given name/value lengths.
     */
    static List<Header> createHeaders(int numHeaders, int nameLength, int valueLength, boolean limitToAscii) {
        List<Header> headers = new ArrayList<Header>(numHeaders);
        Random r = new Random();
        for (int i = 0; i < numHeaders; ++i) {
            byte[] name = randomBytes(new byte[nameLength], limitToAscii);
            byte[] value = randomBytes(new byte[valueLength], limitToAscii);
            headers.add(new Header(name, value));
        }
        return headers;
    }

    private static byte[] randomBytes(byte[] bytes, boolean limitToAscii) {
        Random r = new Random();
        if (limitToAscii) {
            for (int index=0; index < bytes.length; ++index) {
                int charIndex = r.nextInt(ALPHABET.length());
                bytes[index] = (byte) ALPHABET.charAt(charIndex);
            }
        } else {
            r.nextBytes(bytes);
        }
        return bytes;
    }
}


/*
 * Copyright 2014 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack;

import static com.twitter.hpack.HeaderField.HEADER_ENTRY_OVERHEAD;

final class DynamicTable {

  // a circular queue of header fields
  HeaderField[] headerFields;
  int head;
  int tail;
  private int size;
  private int capacity = -1; // ensure setCapacity creates the array

  /**
   * Creates a new dynamic table with the specified initial capacity.
   */
  DynamicTable(int initialCapacity) {
    setCapacity(initialCapacity);
  }

  /**
   * Return the number of header fields in the dynamic table.
   */
  public int length() {
    int length;
    if (head < tail) {
      length = headerFields.length - tail + head;
    } else {
      length = head - tail;
    }
    return length;
  }

  /**
   * Return the current size of the dynamic table.
   * This is the sum of the size of the entries.
   */
  public int size() {
    return size;
  }

  /**
   * Return the maximum allowable size of the dynamic table.
   */
  public int capacity() {
    return capacity;
  }

  /**
   * Return the header field at the given index.
   * The first and newest entry is always at index 1,
   * and the oldest entry is at the index length().
   */
  public HeaderField getEntry(int index) {
    if (index <= 0 || index > length()) {
      throw new IndexOutOfBoundsException();
    }
    int i = head - index;
    if (i < 0) {
      return headerFields[i + headerFields.length];
    } else {
      return headerFields[i];
    }
  }

  /**
   * Add the header field to the dynamic table.
   * Entries are evicted from the dynamic table until the size of the table
   * and the new header field is less than or equal to the table's capacity.
   * If the size of the new entry is larger than the table's capacity,
   * the dynamic table will be cleared.
   */
  public void add(HeaderField header) {
    int headerSize = header.size();
    if (headerSize > capacity) {
      clear();
      return;
    }
    while (size + headerSize > capacity) {
      remove();
    }
    headerFields[head++] = header;
    size += header.size();
    if (head == headerFields.length) {
      head = 0;
    }
  }

  /**
   * Remove and return the oldest header field from the dynamic table.
   */
  public HeaderField remove() {
    HeaderField removed = headerFields[tail];
    if (removed == null) {
      return null;
    }
    size -= removed.size();
    headerFields[tail++] = null;
    if (tail == headerFields.length) {
      tail = 0;
    }
    return removed;
  }

  /**
   * Remove all entries from the dynamic table.
   */
  public void clear() {
    while (tail != head) {
      headerFields[tail++] = null;
      if (tail == headerFields.length) {
        tail = 0;
      }
    }
    head = 0;
    tail = 0;
    size = 0;
  }

  /**
   * Set the maximum size of the dynamic table.
   * Entries are evicted from the dynamic table until the size of the table
   * is less than or equal to the maximum size.
   */
  public void setCapacity(int capacity) {
    if (capacity < 0) {
      throw new IllegalArgumentException("Illegal Capacity: "+ capacity);
    }

    // initially capacity will be -1 so init won't return here
    if (this.capacity == capacity) {
      return;
    }
    this.capacity = capacity;

    if (capacity == 0) {
      clear();
    } else {
      // initially size will be 0 so remove won't be called
      while (size > capacity) {
        remove();
      }
    }

    int maxEntries = capacity / HEADER_ENTRY_OVERHEAD;
    if (capacity % HEADER_ENTRY_OVERHEAD != 0) {
      maxEntries++;
    }

    // check if capacity change requires us to reallocate the array
    if (headerFields != null && headerFields.length == maxEntries) {
      return;
    }

    HeaderField[] tmp = new HeaderField[maxEntries];

    // initially length will be 0 so there will be no copy
    int len = length();
    int cursor = tail;
    for (int i = 0; i < len; i++) {
      HeaderField entry = headerFields[cursor++];
      tmp[i] = entry;
      if (cursor == headerFields.length) {
        cursor = 0;
      }
    }

    this.tail = 0;
    this.head = tail + len;
    this.headerFields = tmp;
  }
}


/*
 * Copyright 2014 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;

import com.twitter.hpack.HpackUtil.IndexType;

public final class Encoder {

  private static final int BUCKET_SIZE = 17;
  private static final byte[] EMPTY = {};

  // for testing
  private final boolean useIndexing;
  private final boolean forceHuffmanOn;
  private final boolean forceHuffmanOff;

  // a linked hash map of header fields
  private final HeaderEntry[] headerFields = new HeaderEntry[BUCKET_SIZE];
  private final HeaderEntry head = new HeaderEntry(-1, EMPTY, EMPTY, Integer.MAX_VALUE, null);
  private int size;
  private int capacity;

  /**
   * Creates a new encoder.
   */
  public Encoder(int maxHeaderTableSize) {
    this(maxHeaderTableSize, true, false, false);
  }

  /**
   * Constructor for testing only.
   */
  Encoder(
      int maxHeaderTableSize,
      boolean useIndexing,
      boolean forceHuffmanOn,
      boolean forceHuffmanOff
  ) {
    if (maxHeaderTableSize < 0) {
      throw new IllegalArgumentException("Illegal Capacity: " + maxHeaderTableSize);
    }
    this.useIndexing = useIndexing;
    this.forceHuffmanOn = forceHuffmanOn;
    this.forceHuffmanOff = forceHuffmanOff;
    this.capacity = maxHeaderTableSize;
    head.before = head.after = head;
  }

  /**
   * Encode the header field into the header block.
   */
  public void encodeHeader(OutputStream out, byte[] name, byte[] value, boolean sensitive) throws IOException {

    // If the header value is sensitive then it must never be indexed
    if (sensitive) {
      int nameIndex = getNameIndex(name);
      encodeLiteral(out, name, value, IndexType.NEVER, nameIndex);
      return;
    }

    // If the peer will only use the static table
    if (capacity == 0) {
      int staticTableIndex = StaticTable.getIndex(name, value);
      if (staticTableIndex == -1) {
        int nameIndex = StaticTable.getIndex(name);
        encodeLiteral(out, name, value, IndexType.NONE, nameIndex);
      } else {
        encodeInteger(out, 0x80, 7, staticTableIndex);
      }
      return;
    }

    int headerSize = HeaderField.sizeOf(name, value);

    // If the headerSize is greater than the max table size then it must be encoded literally
    if (headerSize > capacity) {
      int nameIndex = getNameIndex(name);
      encodeLiteral(out, name, value, IndexType.NONE, nameIndex);
      return;
    }

    HeaderEntry headerField = getEntry(name, value);
    if (headerField != null) {
      int index = getIndex(headerField.index) + StaticTable.length;
      // Section 6.1. Indexed Header Field Representation
      encodeInteger(out, 0x80, 7, index);
    } else {
      int staticTableIndex = StaticTable.getIndex(name, value);
      if (staticTableIndex != -1) {
        // Section 6.1. Indexed Header Field Representation
        encodeInteger(out, 0x80, 7, staticTableIndex);
      } else {
        int nameIndex = getNameIndex(name);
        if (useIndexing) {
          ensureCapacity(headerSize);
        }
        IndexType indexType = useIndexing ? IndexType.INCREMENTAL : IndexType.NONE;
        encodeLiteral(out, name, value, indexType, nameIndex);
        if (useIndexing) {
          add(name, value);
        }
      }
    }
  }

  /**
   * Set the maximum table size.
   */
  public void setMaxHeaderTableSize(OutputStream out, int maxHeaderTableSize) throws IOException {
    if (maxHeaderTableSize < 0) {
      throw new IllegalArgumentException("Illegal Capacity: " + maxHeaderTableSize);
    }
    if (capacity == maxHeaderTableSize) {
      return;
    }
    capacity = maxHeaderTableSize;
    ensureCapacity(0);
    encodeInteger(out, 0x20, 5, maxHeaderTableSize);
  }

  /**
   * Return the maximum table size.
   */
  public int getMaxHeaderTableSize() {
    return capacity;
  }

  /**
   * Encode integer according to Section 5.1.
   */
  private static void encodeInteger(OutputStream out, int mask, int n, int i) throws IOException {
    if (n < 0 || n > 8) {
      throw new IllegalArgumentException("N: " + n);
    }
    int nbits = 0xFF >>> (8 - n);
    if (i < nbits) {
      out.write(mask | i);
    } else {
      out.write(mask | nbits);
      int length = i - nbits;
      while (true) {
        if ((length & ~0x7F) == 0) {
          out.write(length);
          return;
        } else {
          out.write((length & 0x7F) | 0x80);
          length >>>= 7;
        }
      }
    }
  }

  /**
   * Encode string literal according to Section 5.2.
   */
  private void encodeStringLiteral(OutputStream out, byte[] string) throws IOException {
    int huffmanLength = Huffman.ENCODER.getEncodedLength(string);
    if ((huffmanLength < string.length && !forceHuffmanOff) || forceHuffmanOn) {
      encodeInteger(out, 0x80, 7, huffmanLength);
      Huffman.ENCODER.encode(out, string);
    } else {
      encodeInteger(out, 0x00, 7, string.length);
      out.write(string, 0, string.length);
    }
  }

  /**
   * Encode literal header field according to Section 6.2.
   */
  private void encodeLiteral(OutputStream out, byte[] name, byte[] value, IndexType indexType, int nameIndex)
      throws IOException {
    int mask;
    int prefixBits;
    switch(indexType) {
    case INCREMENTAL:
      mask = 0x40;
      prefixBits = 6;
      break;
    case NONE:
      mask = 0x00;
      prefixBits = 4;
      break;
    case NEVER:
      mask = 0x10;
      prefixBits = 4;
      break;
    default:
      throw new IllegalStateException("should not reach here");
    }
    encodeInteger(out, mask, prefixBits, nameIndex == -1 ? 0 : nameIndex);
    if (nameIndex == -1) {
      encodeStringLiteral(out, name);
    }
    encodeStringLiteral(out, value);
  }

  private int getNameIndex(byte[] name) {
    int index = StaticTable.getIndex(name);
    if (index == -1) {
      index = getIndex(name);
      if (index >= 0) {
        index += StaticTable.length;
      }
    }
    return index;
  }

  /**
   * Ensure that the dynamic table has enough room to hold 'headerSize' more bytes.
   * Removes the oldest entry from the dynamic table until sufficient space is available.
   */
  private void ensureCapacity(int headerSize) throws IOException {
    while (size + headerSize > capacity) {
      int index = length();
      if (index == 0) {
        break;
      }
      remove();
    }
  }

  /**
   * Return the number of header fields in the dynamic table.
   * Exposed for testing.
   */
  int length() {
    return size == 0 ? 0 : head.after.index - head.before.index + 1;
  }

  /**
   * Return the size of the dynamic table.
   * Exposed for testing.
   */
  int size() {
    return size;
  }

  /**
   * Return the header field at the given index.
   * Exposed for testing.
   */
  HeaderField getHeaderField(int index) {
    HeaderEntry entry = head;
    while(index-- >= 0) {
      entry = entry.before;
    }
    return entry;
  }

  /**
   * Returns the header entry with the lowest index value for the header field.
   * Returns null if header field is not in the dynamic table.
   */
  private HeaderEntry getEntry(byte[] name, byte[] value) {
    if (length() == 0 || name == null || value == null) {
      return null;
    }
    int h = hash(name);
    int i = index(h);
    for (HeaderEntry e = headerFields[i]; e != null; e = e.next) {
      if (e.hash == h &&
          HpackUtil.equals(name, e.name) &&
          HpackUtil.equals(value, e.value)) {
        return e;
      }
    }
    return null;
  }

  /**
   * Returns the lowest index value for the header field name in the dynamic table.
   * Returns -1 if the header field name is not in the dynamic table.
   */
  private int getIndex(byte[] name) {
    if (length() == 0 || name == null) {
      return -1;
    }
    int h = hash(name);
    int i = index(h);
    int index = -1;
    for (HeaderEntry e = headerFields[i]; e != null; e = e.next) {
      if (e.hash == h && HpackUtil.equals(name, e.name)) {
        index = e.index;
        break;
      }
    }
    return getIndex(index);
  }

  /**
   * Compute the index into the dynamic table given the index in the header entry.
   */
  private int getIndex(int index) {
    if (index == -1) {
      return index;
    }
    return index - head.before.index + 1;
  }

  /**
   * Add the header field to the dynamic table.
   * Entries are evicted from the dynamic table until the size of the table
   * and the new header field is less than the table's capacity.
   * If the size of the new entry is larger than the table's capacity,
   * the dynamic table will be cleared.
   */
  private void add(byte[] name, byte[] value) {
    int headerSize = HeaderField.sizeOf(name, value);

    // Clear the table if the header field size is larger than the capacity.
    if (headerSize > capacity) {
      clear();
      return;
    }

    // Evict oldest entries until we have enough capacity.
    while (size + headerSize > capacity) {
      remove();
    }

    // Copy name and value that modifications of original do not affect the dynamic table.
    name = Arrays.copyOf(name, name.length);
    value = Arrays.copyOf(value, value.length);

    int h = hash(name);
    int i = index(h);
    HeaderEntry old = headerFields[i];
    HeaderEntry e = new HeaderEntry(h, name, value, head.before.index - 1, old);
    headerFields[i] = e;
    e.addBefore(head);
    size += headerSize;
  }

  /**
   * Remove and return the oldest header field from the dynamic table.
   */
  private HeaderField remove() {
    if (size == 0) {
      return null;
    }
    HeaderEntry eldest = head.after;
    int h = eldest.hash;
    int i = index(h);
    HeaderEntry prev = headerFields[i];
    HeaderEntry e = prev;
    while (e != null) {
      HeaderEntry next = e.next;
      if (e == eldest) {
        if (prev == eldest) {
          headerFields[i] = next;
        } else {
          prev.next = next;
        }
        eldest.remove();
        size -= eldest.size();
        return eldest;
      }
      prev = e;
      e = next;
    }
    return null;
  }

  /**
   * Remove all entries from the dynamic table.
   */
  private void clear() {
    Arrays.fill(headerFields, null);
    head.before = head.after = head;
    this.size = 0;
  }

  /**
   * Returns the hash code for the given header field name.
   */
  private static int hash(byte[] name) {
    int h = 0;
    for (int i = 0; i < name.length; i++) {
      h = 31 * h + name[i];
    }
    if (h > 0) {
      return h;
    } else if (h == Integer.MIN_VALUE) {
      return Integer.MAX_VALUE;
    } else {
      return -h;
    }
  }

  /**
   * Returns the index into the hash table for the hash code h.
   */
  private static int index(int h) {
    return h % BUCKET_SIZE;
  }

  /**
   * A linked hash map HeaderField entry.
   */
  private static class HeaderEntry extends HeaderField {
    // These fields comprise the doubly linked list used for iteration.
    HeaderEntry before, after;

    // These fields comprise the chained list for header fields with the same hash.
    HeaderEntry next;
    int hash;

    // This is used to compute the index in the dynamic table.
    int index;

    /**
     * Creates new entry.
     */
    HeaderEntry(int hash, byte[] name, byte[] value, int index, HeaderEntry next) {
      super(name, value);
      this.index = index;
      this.hash = hash;
      this.next = next;
    }

    /**
     * Removes this entry from the linked list.
     */
    private void remove() {
      before.after = after;
      after.before = before;
      before = null; // null reference to prevent nepotism with generational GC.
      after = null;  // null reference to prevent nepotism with generational GC.
      next = null;   // null reference to prevent nepotism with generational GC.
    }

    /**
     * Inserts this entry before the specified existing entry in the list.
     */
    private void addBefore(HeaderEntry existingEntry) {
      after  = existingEntry;
      before = existingEntry.before;
      before.after = this;
      after.before = this;
    }
  }
}


/*
 * Copyright 2014 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

final class HuffmanDecoder {

  private static final IOException EOS_DECODED = new IOException("EOS Decoded");
  private static final IOException INVALID_PADDING = new IOException("Invalid Padding");

  private final Node root;

  /**
   * Creates a new Huffman decoder with the specified Huffman coding.
   * @param codes   the Huffman codes indexed by symbol
   * @param lengths the length of each Huffman code
   */
  HuffmanDecoder(int[] codes, byte[] lengths) {
    if (codes.length != 257 || codes.length != lengths.length) {
      throw new IllegalArgumentException("invalid Huffman coding");
    }
    root = buildTree(codes, lengths);
  }

  /**
   * Decompresses the given Huffman coded string literal.
   * @param  buf the string literal to be decoded
   * @return the output stream for the compressed data
   * @throws IOException if an I/O error occurs. In particular,
   *         an IOException may be thrown if the
   *         output stream has been closed.
   */
  public byte[] decode(byte[] buf) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    Node node = root;
    int current = 0;
    int bits = 0;
    for (int i = 0; i < buf.length; i++) {
      int b = buf[i] & 0xFF;
      current = (current << 8) | b;
      bits += 8;
      while (bits >= 8) {
        int c = (current >>> (bits - 8)) & 0xFF;
        node = node.children[c];
        bits -= node.bits;
        if (node.isTerminal()) {
          if (node.symbol == HpackUtil.HUFFMAN_EOS) {
            throw EOS_DECODED;
          }
          baos.write(node.symbol);
          node = root;
        }
      }
    }

    while (bits > 0) {
      int c = (current << (8 - bits)) & 0xFF;
      node = node.children[c];
      if (node.isTerminal() && node.bits <= bits) {
        bits -= node.bits;
        baos.write(node.symbol);
        node = root;
      } else {
        break;
      }
    }

    // Section 5.2. String Literal Representation
    // Padding not corresponding to the most significant bits of the code
    // for the EOS symbol (0xFF) MUST be treated as a decoding error.
    int mask = (1 << bits) - 1;
    if ((current & mask) != mask) {
      throw INVALID_PADDING;
    }

    return baos.toByteArray();
  }

  private static final class Node {

    private final int symbol;      // terminal nodes have a symbol
    private final int bits;        // number of bits matched by the node
    private final Node[] children; // internal nodes have children

    /**
     * Construct an internal node
     */
    private Node() {
      symbol = 0;
      bits = 8;
      children = new Node[256];
    }

    /**
     * Construct a terminal node
     * @param symbol the symbol the node represents
     * @param bits   the number of bits matched by this node
     */
    private Node(int symbol, int bits) {
      assert(bits > 0 && bits <= 8);
      this.symbol = symbol;
      this.bits = bits;
      children = null;
    }

    private boolean isTerminal() {
      return children == null;
    }
  }

  private static Node buildTree(int[] codes, byte[] lengths) {
    Node root = new Node();
    for (int i = 0; i < codes.length; i++) {
      insert(root, i, codes[i], lengths[i]);
    }
    return root;
  }

  private static void insert(Node root, int symbol, int code, byte length) {
    // traverse tree using the most significant bytes of code
    Node current = root;
    while (length > 8) {
      if (current.isTerminal()) {
        throw new IllegalStateException("invalid Huffman code: prefix not unique");
      }
      length -= 8;
      int i = (code >>> length) & 0xFF;
      if (current.children[i] == null) {
        current.children[i] = new Node();
      }
      current = current.children[i];
    }

    Node terminal = new Node(symbol, length);
    int shift = 8 - length;
    int start = (code << shift) & 0xFF;
    int end = 1 << shift;
    for (int i = start; i < start + end; i++) {
      current.children[i] = terminal;
    }
  }
}


/*
 * Copyright 2015 Twitter, Inc.
 *
 * 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.
 */
package com.twitter.hpack.microbench;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.junit.Test;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.OptionsBuilder;

/**
 * Base class for all JMH benchmarks.
 */
@Warmup(iterations = AbstractMicrobenchmarkBase.DEFAULT_WARMUP_ITERATIONS)
@Measurement(iterations = AbstractMicrobenchmarkBase.DEFAULT_MEASURE_ITERATIONS)
@Fork(value = AbstractMicrobenchmarkBase.DEFAULT_FORKS)
@State(Scope.Benchmark)
public abstract class AbstractMicrobenchmarkBase {
    private static final Logger logger = Logger.getLogger(AbstractMicrobenchmarkBase.class.getName());

    protected static final int DEFAULT_WARMUP_ITERATIONS = 5;
    protected static final int DEFAULT_MEASURE_ITERATIONS = 5;
    protected static final int DEFAULT_FORKS = 1;
    protected static final String[] JVM_ARGS = {
            "-server", "-dsa", "-da", "-XX:+AggressiveOpts", "-XX:+UseBiasedLocking",
            "-XX:+UseFastAccessorMethods", "-XX:+OptimizeStringConcat",
            "-XX:+HeapDumpOnOutOfMemoryError"};

    /**
     * Internal key used to index a particular set of headers in the map.
     */
    private static class HeadersKey {
        final HeadersSize size;
        final boolean limitToAscii;

        public HeadersKey(HeadersSize size, boolean limitToAscii) {
            this.size = size;
            this.limitToAscii = limitToAscii;
        }

        List<Header> newHeaders() {
            return size.newHeaders(limitToAscii);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            HeadersKey that = (HeadersKey) o;

            if (limitToAscii != that.limitToAscii) return false;
            return size == that.size;

        }

        @Override
        public int hashCode() {
            int result = size.hashCode();
            result = 31 * result + (limitToAscii ? 1 : 0);
            return result;
        }
    }

    private static final Map<HeadersKey, List<Header>> headersMap;
    static {
        HeadersSize[] sizes = HeadersSize.values();
        headersMap = new HashMap<HeadersKey, List<Header>>(sizes.length * 2);
        for (HeadersSize size : sizes) {
            HeadersKey key = new HeadersKey(size, true);
            headersMap.put(key, key.newHeaders());

            key = new HeadersKey(size, false);
            headersMap.put(key, key.newHeaders());
        }
    }

    /**
     * Gets headers for the given size and whether the key/values should be limited to ASCII.
     */
    protected static List<Header> headers(HeadersSize size, boolean limitToAscii) {
        return headersMap.get(new HeadersKey(size, limitToAscii));
    }

    protected ChainedOptionsBuilder newOptionsBuilder() throws Exception {
        String className = getClass().getSimpleName();

        ChainedOptionsBuilder runnerOptions = new OptionsBuilder()
                .include(".*" + className + ".*")
                .jvmArgs(jvmArgs());

        if (getWarmupIterations() > 0) {
            runnerOptions.warmupIterations(getWarmupIterations());
        }

        if (getMeasureIterations() > 0) {
            runnerOptions.measurementIterations(getMeasureIterations());
        }

        if (getForks() > 0) {
            runnerOptions.forks(getForks());
        }

        if (getReportDir() != null) {
            String filePath = getReportDir() + className + ".json";
            File file = new File(filePath);
            if (file.exists()) {
                file.delete();
            } else {
                file.getParentFile().mkdirs();
                file.createNewFile();
            }

            runnerOptions.resultFormat(ResultFormatType.JSON);
            runnerOptions.result(filePath);
        }

        return runnerOptions;
    }

    protected String[] jvmArgs() {
        return JVM_ARGS;
    }

    @Test
    public void run() throws Exception {
        new Runner(newOptionsBuilder().build()).run();
    }

    private int getWarmupIterations() {
        return getIntProperty("warmupIterations", -1);
    }

    private int getMeasureIterations() {
        return getIntProperty("measureIterations", -1);
    }

    private int getForks() {
        return getIntProperty("forks", -1);
    }

    private String getReportDir() {
        return System.getProperty("perfReportDir");
    }

    private static int getIntProperty(String key, int def) {
        String value = System.getProperty(key);
        if (value == null) {
            return def;
        }

        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            logger.warning(
                    "Unable to parse the integer system property '" + key + "':" + value + " - " +
                            "using the default value: " + def);
        }

        return def;
    }
}


package me.bazhenov.groovysh.spring;

import me.bazhenov.groovysh.GroovyShellService;
import me.bazhenov.groovysh.thread.ServerSessionAwareThreadFactory;

import org.apache.sshd.server.auth.password.PasswordAuthenticator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanIsAbstractException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.HashMap;
import java.util.Map;

import static java.util.Arrays.asList;

@SuppressWarnings("UnusedDeclaration")
public class GroovyShellServiceBean implements InitializingBean, DisposableBean, ApplicationContextAware {

	private final GroovyShellService service;
	private ApplicationContext applicationContext;
	private boolean launchAtStart = true;
	private boolean publishContextBeans = true;

	public GroovyShellServiceBean() {
		this.service = newService();
	}

	protected GroovyShellService newService() {
		return new GroovyShellService();
	}

	public void setPort(int port) {
		service.setPort(port);
	}

	public void setLaunchAtStart(boolean launchAtStart) {
		this.launchAtStart = launchAtStart;
	}

	/**
	 * @param publishContextBeans should spring beans be published in groovysh context
	 */
	public void setPublishContextBeans(boolean publishContextBeans) {
		this.publishContextBeans = publishContextBeans;
	}

	public boolean isLaunchAtStart() {
		return launchAtStart;
	}

	public void setBindings(Map<String, Object> bindings) {
		service.setBindings(bindings);
	}

	protected GroovyShellService getService() {
		return service;
	}

	public void setPasswordAuthenticator(PasswordAuthenticator passwordAuthenticator) {
		service.setPasswordAuthenticator(passwordAuthenticator);
	}

	public void setThreadFactory(ServerSessionAwareThreadFactory threadFactory) {
	    service.setThreadFactory(threadFactory);
	}

	/**
	 * Set the comma delimited list of default scripts
	 *
	 * @param scriptNames script names
	 */
	public void setDefaultScriptNames(String scriptNames) {
		if (!scriptNames.trim().isEmpty())
			service.setDefaultScripts(asList(scriptNames.split(",")));
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	@Override
	public void destroy() throws Exception {
		service.destroy();
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if (launchAtStart) {
			if (applicationContext != null) {
				Map<String, Object> bindings = new HashMap<String, Object>();
				if (publishContextBeans)
					publishContextBeans(bindings, applicationContext);
				bindings.put("ctx", applicationContext);
				if (service.getBindings() != null)
					bindings.putAll(service.getBindings());
				service.setBindings(bindings);
			}
			service.start();
		}
	}

	private static void publishContextBeans(Map<String, Object> bindings, ApplicationContext ctx) {
		for (String name : ctx.getBeanDefinitionNames()) {
			if (!name.contains("#")) { // skip beans without explicit id given
				try {
					bindings.put(name, ctx.getBean(name));
				} catch (BeanIsAbstractException exc) {
					// Skip abstract beans
				}
			}
		}
	}
}


/*
 * Created on Oct 20, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions.internal;

import static org.fest.assertions.error.ShouldBeEqual.shouldBeEqual;
import static org.fest.assertions.error.ShouldBeLessThan.shouldBeLessThan;
import static org.fest.assertions.error.ShouldNotBeEqual.shouldNotBeEqual;
import static org.fest.assertions.error.ShouldNotBeGreaterThan.shouldNotBeGreaterThan;
import static org.fest.assertions.error.ShouldNotBeLessThan.shouldNotBeLessThan;
import static org.fest.assertions.internal.Comparables.assertNotNull;

import org.fest.assertions.description.Description;
import org.fest.assertions.error.ShouldBeGreaterThan;
import org.fest.util.VisibleForTesting;

/**
 * Reusable assertions for {@link Short}s.
 *
 * @author Alex Ruiz
 * @author Yvonne Wang
 */
public class Shorts {

  private static final Shorts INSTANCE = new Shorts();

  /**
   * Returns the singleton instance of this class.
   *
   * @return the singleton instance of this class.
   */
  public static Shorts instance() {
    return INSTANCE;
  }

  @VisibleForTesting
  Comparables comparables = Comparables.instance();
  @VisibleForTesting
  Failures failures = Failures.instance();

  @VisibleForTesting
  Shorts() {
  }

  /**
   * Verifies that two shorts are equal.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertEqualTo(Description description, Short actual, short expected) {
    assertNotNull(description, actual);
    if (!isEqualTo(actual, expected)) {
      throw failures.failure(description, shouldBeEqual(actual, expected));
    }
  }

  /**
   * Verifies that two integers are not equal.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertNotEqualTo(Description description, Short actual, short expected) {
    assertNotNull(description, actual);
    if (isEqualTo(actual, expected)) {
      throw failures.failure(description, shouldNotBeEqual(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is greater than the expected one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertGreaterThan(Description description, Short actual, short expected) {
    assertNotNull(description, actual);
    if (!isGreaterThan(actual, expected)) {
      throw failures.failure(description, ShouldBeGreaterThan.shouldBeGreaterThan(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is greater than or equal to the expected one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertNotLessThan(Description description, Short actual, short expected) {
    assertNotNull(description, actual);
    if (isLessThan(actual, expected)) {
      throw failures.failure(description, shouldNotBeLessThan(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is less than the expected one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertLessThan(Description description, Short actual, short expected) {
    assertNotNull(description, actual);
    if (!isLessThan(actual, expected)) {
      throw failures.failure(description, shouldBeLessThan(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is less than or equal to the expected one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertNotGreaterThan(Description description, Short actual, short expected) {
    assertNotNull(description, actual);
    if (isGreaterThan(actual, expected)) {
      throw failures.failure(description, shouldNotBeGreaterThan(actual, expected));
    }
  }

  private boolean isEqualTo(Short actual, short expected) {
    return actual.intValue() == expected;
  }

  private boolean isGreaterThan(Short actual, short expected) {
    return actual.intValue() > expected;
  }

  private boolean isLessThan(Short actual, short expected) {
    return actual.intValue() < expected;
  }
}


/*
 * Created on Mar 29, 2009
 *
 * 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.
 *
 * Copyright @2013 the original author or authors.
 */
package org.fest.assertions.api;

import org.fest.assertions.core.Assert;
import org.fest.assertions.description.Description;
import org.fest.assertions.internal.Matchers;
import org.fest.assertions.internal.Objects;
import org.fest.util.VisibleForTesting;

/**
 * Base class for all assertions, except {@link MapAssert}.
 *
 * @param <S> the "self" type of this assertion class. Please read "Emulating 'self types' using Java Generics to simplify fluent API implementation"
 *            for more details.
 * @param <T> the type of the actual value.
 * @author Alex Ruiz
 * @author Joel Costigliola
 * @author Yvonne Wang
 */
public abstract class AbstractAssert<S, T> implements Assert<S, T> {
  protected final T actual;
  protected final S myself;
  protected final Description description;
  @VisibleForTesting
  Objects objects = Objects.instance();
  @VisibleForTesting
  Matchers matchers = Matchers.instance();

  protected AbstractAssert(T actual, Class<S> selfType) {
    this(actual, selfType, null);
  }

  protected AbstractAssert(T actual, Class<S> selfType, Description description) {
    myself = selfType.cast(this);
    this.actual = actual;
    this.description = description;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public S isEqualTo(T expected) {
    objects.assertEqual(description, actual, expected);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public S isNotEqualTo(T other) {
    objects.assertNotEqual(description, actual, other);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public S isNull() {
    objects.assertNull(description, actual);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public S isNotNull() {
    objects.assertNotNull(description, actual);
    return myself;
  }

  /**
   * Returns the description of the actual value.
   *
   * @return the description of the actual value, or {@code null} if such description was not specified during
   *         construction.
   */
  public String descriptionText() {
    return description != null ? description.value() : null;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final boolean equals(Object obj) {
    throw new UnsupportedOperationException("'equals' is not supported...maybe you intended to call 'isEqualTo'");
  }

  /**
   * Always returns 1.
   *
   * @return 1.
   */
  @Override
  public final int hashCode() {
    return 1;
  }
}


/*
 * Created on Oct 17, 2010
 *
 * 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.
 *
 * Copyright @2010-2012 the original author or authors.
 */
package org.fest.assertions.internal;

import static org.fest.assertions.error.NotEqualErrorFactory.shouldBeEqual;
import static org.fest.assertions.error.ShouldBeGreaterThan.shouldBeGreaterThan;
import static org.fest.assertions.error.ShouldBeLessThan.shouldBeLessThan;
import static org.fest.assertions.error.ShouldNotBeEqual.shouldNotBeEqual;
import static org.fest.assertions.error.ShouldNotBeGreaterThan.shouldNotBeGreaterThan;
import static org.fest.assertions.error.ShouldNotBeLessThan.shouldNotBeLessThan;

import org.fest.assertions.description.Description;
import org.fest.util.VisibleForTesting;

/**
 * Reusable assertions for {@link Comparable}s.
 *
 * @author Alex Ruiz
 */
public class Comparables {
  private static final Comparables INSTANCE = new Comparables();

  public static Comparables instance() {
    return INSTANCE;
  }

  @VisibleForTesting
  Failures failures = Failures.instance();

  @VisibleForTesting
  Comparables() {}

  @VisibleForTesting
  void setFailures(Failures failures) {
    this.failures = failures;
  }

  /**
   * Asserts that two values are equal by invoking {@link Comparable#compareTo(Object)}.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is not equal to the expected one. This method will throw a
   *           {@code org.junit.ComparisonFailure} instead if JUnit is in the classpath and the expected and actual
   *           values are not equal.
   */
  public <T extends Comparable<T>> void assertEqual(Description description, T actual, T expected) {
    assertNotNull(description, actual);
    if (!areEqual(actual, expected)) {
      throw shouldBeEqual(actual, expected).newAssertionError(description);
    }
  }

  /**
   * Asserts that two values are not equal by invoking {@link Comparable#compareTo(Object)}.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param other the value to compare the actual value to.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is equal to the other one.
   */
  public <T extends Comparable<T>> void assertNotEqual(Description description, T actual, T other) {
    assertNotNull(description, actual);
    if (areEqual(actual, other)) {
      throw failures.failure(description, shouldNotBeEqual(actual, other));
    }
  }

  private <T extends Comparable<T>> boolean areEqual(T actual, T expected) {
    return actual.compareTo(expected) == 0;
  }

  /**
   * Asserts that the actual value is less than the other one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param other the value to compare the actual value to.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is not less than the other one.
   */
  public <T extends Comparable<T>> void assertLessThan(Description description, T actual, T other) {
    assertNotNull(description, actual);
    if (!isLessThan(actual, other)) {
      throw failures.failure(description, shouldBeLessThan(actual, other));
    }
  }

  /**
   * Asserts that the actual value is greater than or equal to the other one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param other the value to compare the actual value to.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is less than the other one.
   */
  public <T extends Comparable<T>> void assertNotLessThan(Description description, T actual, T other) {
    assertNotNull(description, actual);
    if (isLessThan(actual, other)) {
      throw failures.failure(description, shouldNotBeLessThan(actual, other));
    }
  }

  private static <T extends Comparable<T>> boolean isLessThan(T actual, T other) {
    return actual.compareTo(other) < 0;
  }

  /**
   * Asserts that the actual value is greater than the other one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param other the value to compare the actual value to.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is not greater than the other one.
   */
  public <T extends Comparable<T>> void assertGreaterThan(Description description, T actual, T other) {
    assertNotNull(description, actual);
    if (!isGreaterThan(actual, other)) {
      throw failures.failure(description, shouldBeGreaterThan(actual, other));
    }
  }

  /**
   * Asserts that the actual value is less than or equal to the other one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param other the value to compare the actual value to.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is greater than the other one.
   */
  public <T extends Comparable<T>> void assertNotGreaterThan(Description description, T actual, T other) {
    assertNotNull(description, actual);
    if (isGreaterThan(actual, other)) {
      throw failures.failure(description, shouldNotBeGreaterThan(actual, other));
    }
  }

  private static <T extends Comparable<T>> boolean isGreaterThan(T actual, T other) {
    return actual.compareTo(other) > 0;
  }

  protected static <T> void assertNotNull(Description description, T actual) {
    Objects.instance().assertNotNull(description, actual);
  }
}


/*
 * Created on Oct 24, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions.internal;

import static java.lang.Math.abs;

import static org.fest.assertions.error.ShouldBeEqual.shouldBeEqual;
import static org.fest.assertions.error.ShouldBeEqualWithinOffset.shouldBeEqual;
import static org.fest.assertions.error.ShouldBeGreaterThan.shouldBeGreaterThan;
import static org.fest.assertions.error.ShouldBeLessThan.shouldBeLessThan;
import static org.fest.assertions.error.ShouldNotBeEqual.shouldNotBeEqual;
import static org.fest.assertions.error.ShouldNotBeGreaterThan.shouldNotBeGreaterThan;
import static org.fest.assertions.error.ShouldNotBeLessThan.shouldNotBeLessThan;
import static org.fest.assertions.internal.CommonValidations.checkNumberIsNotNull;
import static org.fest.assertions.internal.CommonValidations.checkOffsetIsNotNull;
import static org.fest.assertions.internal.Comparables.assertNotNull;

import org.fest.assertions.data.Offset;
import org.fest.assertions.description.Description;
import org.fest.util.VisibleForTesting;

/**
 * Reusable assertions for {@link Float}s.
 *
 * @author Alex Ruiz
 * @author Yvonne Wang
 */
public class Floats {

  private static final Floats INSTANCE = new Floats();

  /**
   * Returns the singleton instance of this class.
   *
   * @return the singleton instance of this class.
   */
  public static Floats instance() {
    return INSTANCE;
  }

  @VisibleForTesting
  Floats() {
  }

  @VisibleForTesting
  Comparables comparables = Comparables.instance();
  @VisibleForTesting
  Failures failures = Failures.instance();

  /**
   * Verifies that the actual value is equal to {@code NaN}.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   */
  public void assertIsNaN(Description description, Float actual) {
    comparables.assertEqual(description, actual, Float.NaN);
  }

  /**
   * Verifies that the actual value is not equal to {@code NaN}.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   */
  public void assertIsNotNaN(Description description, Float actual) {
    comparables.assertNotEqual(description, actual, Float.NaN);
  }

  /**
   * Verifies that two floats are equal within a positive offset.

   * It does not rely on the custom comparisionStrategy (if one is set) because using an offset is already a specific
   * comparison strategy.
   *
   * @param description describes the information about the assertion.
   * @param actual the actual value.
   * @param expected the expected value.
   * @param offset the given positive offset.
   * @throws NullPointerException if the given offset is {@code null}.
   * @throws AssertionError if the actual value is {@code null}.
   * @throws AssertionError if the actual value is not equal to the expected one.
   */
  public void assertEqual(Description description, Float actual, Float expected, Offset<Float> offset) {
    checkOffsetIsNotNull(offset);
    checkNumberIsNotNull(expected);
    assertNotNull(description, actual);
    if (!isEqualTo(actual, expected, offset)) {
      throw failures.failure(description,
          shouldBeEqual(actual, expected, offset, abs(expected.floatValue() - actual.floatValue())));
    }
  }

  public void assertEqual(Description description, Float actual, float expected) {
    assertNotNull(description, actual);
    if (actual.floatValue() != expected) {
      throw failures.failure(description, shouldBeEqual(actual, expected));
    }
  }

  public void assertNotEqual(Description description, Float actual, float expected) {
    assertNotNull(description, actual);
    if (actual.floatValue() == expected) {
      throw failures.failure(description, shouldNotBeEqual(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is great than the expected one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertGreaterThan(Description description, Float actual, float expected) {
    assertNotNull(description, actual);
    if (!isGreaterThan(actual, expected)) {
      throw failures.failure(description, shouldBeGreaterThan(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is less than or equal to the expected.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected valu.e
   */
  public void assertNotGreaterThan(Description description, Float actual, float expected) {
    assertNotNull(description, actual);
    if (isGreaterThan(actual, expected)) {
      throw failures.failure(description, shouldNotBeGreaterThan(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is less than the expected.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected value.
   */
  public void assertLessThan(Description description, Float actual, float expected) {
    assertNotNull(description, actual);
    if (!isLessThan(actual, expected)) {
      throw failures.failure(description, shouldBeLessThan(actual, expected));
    }
  }

  /**
   * Verifies that the actual value is greater than or equal to the expected one.
   *
   * @param description the description of the actual value.
   * @param actual the actual value.
   * @param expected the expected valu.e
   */
  public void assertNotLessThan(Description description, Float actual, float expected) {
    assertNotNull(description, actual);
    if (isLessThan(actual, expected)) {
      throw failures.failure(description, shouldNotBeLessThan(actual, expected));
    }
  }

  private boolean isEqualTo(Float actual, Float expected, Offset<?> offset) {
    return abs(expected.floatValue() - actual.floatValue()) <= offset.value.floatValue();
  }

  private boolean isGreaterThan(Float actual, float expected) {
    return actual.floatValue() > expected;
  }

  private boolean isLessThan(Float actual, float expected) {
    return actual.floatValue() < expected;
  }
}


/*
 * Created on Jan 11, 2011
 *
 * 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.
 *
 * Copyright @2011 the original author or authors.
 */
package org.fest.assertions.error;

import static org.fest.util.Strings.isNullOrEmpty;

import org.fest.assertions.description.Description;
import org.fest.util.VisibleForTesting;

/**
 * Formats the {@link Description}s to be included in assertion errors.
 *
 * @author Alex Ruiz
 */
public class DescriptionFormatter {

  private static final DescriptionFormatter INSTANCE = new DescriptionFormatter();

  /**
   * Returns the singleton instance of this class.
   * @return the singleton instance of this class.
   */
  public static DescriptionFormatter instance() {
    return INSTANCE;
  }

  @VisibleForTesting
  DescriptionFormatter() {}

  /**
   * Formats the given {@link Description} by surrounding its text value with square brackets and adding a space at
   * the end.
   * @param d the description to format. It can be {@code null}.
   * @return the formatted description, or an empty {@code String} if the the {@code Description} is {@code null}.
   */
  public String format(Description d) {
    String s = (d != null) ? d.value() : null;
    if (isNullOrEmpty(s)) return "";
    return String.format("[%s] ", s);
  }

}


/*
 * Created on Nov 18, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions.api;

import java.util.Collection;

import javax.annotation.Nonnull;

import org.fest.assertions.core.ObjectEnumerableAssert;
import org.fest.assertions.description.Description;
import org.fest.assertions.internal.Collections;
import org.fest.util.VisibleForTesting;

/**
 * Base class for implementations of {@link ObjectEnumerableAssert} whose actual value type is {@link Collection}.
 *
 * @param <S> the "self" type of this assertion class. Please read "Emulating 'self types' using Java Generics to simplify fluent API implementation"
 *          for more details.
 * @param <T> the type of the "actual" value.
 * @author Yvonne Wang
 * @author Alex Ruiz
 */
public abstract class AbstractCollectionAssert<S, T extends Collection<?>> extends AbstractAssert<S, T> implements
    ObjectEnumerableAssert<S> {

  @VisibleForTesting
  Collections collections = Collections.instance();

  protected AbstractCollectionAssert(T actual, Class<S> selfType) {
    super(actual, selfType);
  }

  protected AbstractCollectionAssert(T actual, Class<S> selfType, Description description) {
    super(actual, selfType, description);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S isNullOrEmpty() {
    collections.assertNullOrEmpty(description, actual);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S isEmpty() {
    collections.assertEmpty(description, actual);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Nonnull
  @Override
  public final S isNotEmpty() {
    collections.assertNotEmpty(description, actual);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Nonnull
  @Override
  public final S hasSize(int expected) {
    collections.assertHasSize(description, actual, expected);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S contains(Object... values) {
    collections.assertContains(description, actual, values);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S containsOnly(Object... values) {
    collections.assertContainsOnly(description, actual, values);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S containsSequence(Object... sequence) {
    collections.assertContainsSequence(description, actual, sequence);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S doesNotContain(Object... values) {
    collections.assertDoesNotContain(description, actual, values);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S doesNotHaveDuplicates() {
    collections.assertDoesNotHaveDuplicates(description, actual);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S startsWith(Object... sequence) {
    collections.assertStartsWith(description, actual, sequence);
    return myself;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public final S endsWith(Object... sequence) {
    collections.assertEndsWith(description, actual, sequence);
    return myself;
  }

  @Override
  public S containsNull() {
    collections.assertContainsNull(description, actual);
    return myself;
  }

  @Override
  public S doesNotContainNull() {
    collections.assertDoesNotContainNull(description, actual);
    return myself;
  }
}


/*
 * Created on Nov 18, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions.api;

import org.fest.assertions.core.ComparableAssert;
import org.fest.assertions.description.Description;
import org.fest.assertions.internal.Comparables;
import org.fest.util.VisibleForTesting;

/**
 * Base class for all implementations of {@link ComparableAssert}.
 *
 * @param <S> the "self" type of this assertion class. Please read "Emulating 'self types' using Java Generics to simplify fluent API implementation"
 *          for more details.
 * @param  the type of the actual value.
 *
 * @author Alex Ruiz
 */
public abstract class AbstractComparableAssert<S, A extends Comparable> extends AbstractAssert<S, A>
    implements ComparableAssert<S, A> {

  @VisibleForTesting
  Comparables comparables = Comparables.instance();

  protected AbstractComparableAssert(A actual, Class<S> selfType) {
    super(actual, selfType);
  }

  protected AbstractComparableAssert(A actual, Class<S> selfType, Description description) {
    super(actual, selfType, description);
  }

  /** {@inheritDoc} */
  @Override
  public S isEqualTo(A expected) {
    comparables.assertEqual(description, actual, expected);
    return myself;
  }

  /** {@inheritDoc} */
  @Override
  public S isNotEqualTo(A expected) {
    comparables.assertNotEqual(description, actual, expected);
    return myself;
  }

  /** {@inheritDoc} */
  @Override
  public S isLessThan(A other) {
    comparables.assertLessThan(description, actual, other);
    return myself;
  }

  /** {@inheritDoc} */
  @Override
  public S isNotLessThan(A other) {
    comparables.assertNotLessThan(description, actual, other);
    return myself;
  }

  /** {@inheritDoc} */
  @Override
  public S isGreaterThan(A other) {
    comparables.assertGreaterThan(description, actual, other);
    return myself;
  }

  /** {@inheritDoc} */
  @Override
  public S isNotGreaterThan(A other) {
    comparables.assertNotGreaterThan(description, actual, other);
    return myself;
  }
}