alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (Encoder.java)

This example Java source code file (Encoder.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

asciistring, bucket_size, capacity, charsequence, encoder, headerentry, headerfield, huffmanencoder, illegalargumentexception, illegalstateexception, incremental, never, none, util

The Encoder.java Java example source code

/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/*
 * 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 io.netty.handler.codec.http2.internal.hpack;

import io.netty.buffer.ByteBuf;
import io.netty.util.AsciiString;
import io.netty.util.CharsetUtil;

import java.util.Arrays;

import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.INCREMENTAL;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NEVER;
import static io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType.NONE;

public final class Encoder {

    private static final int BUCKET_SIZE = 17;

    // for testing
    private final boolean useIndexing;
    private final boolean forceHuffmanOn;
    private final boolean forceHuffmanOff;
    private final HuffmanEncoder huffmanEncoder = new HuffmanEncoder();

    // a linked hash map of header fields
    private final HeaderEntry[] headerFields = new HeaderEntry[BUCKET_SIZE];
    private final HeaderEntry head = new HeaderEntry(-1, AsciiString.EMPTY_STRING,
            AsciiString.EMPTY_STRING, 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;
        capacity = maxHeaderTableSize;
        head.before = head.after = head;
    }

    /**
     * Encode the header field into the header block.
     *
     * <strong>The given {@link CharSequence}s must be immutable!
     */
    public void encodeHeader(ByteBuf out, CharSequence name, CharSequence value, boolean sensitive) {

        // If the header value is sensitive then it must never be indexed
        if (sensitive) {
            int nameIndex = getNameIndex(name);
            encodeLiteral(out, name, value, 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, 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, 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);
                }
                HpackUtil.IndexType indexType =
                        useIndexing ? INCREMENTAL : NONE;
                encodeLiteral(out, name, value, indexType, nameIndex);
                if (useIndexing) {
                    add(name, value);
                }
            }
        }
    }

    /**
     * Set the maximum table size.
     */
    public void setMaxHeaderTableSize(ByteBuf out, int maxHeaderTableSize) {
        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(ByteBuf out, int mask, int n, int i) {
        if (n < 0 || n > 8) {
            throw new IllegalArgumentException("N: " + n);
        }
        int nbits = 0xFF >>> (8 - n);
        if (i < nbits) {
            out.writeByte(mask | i);
        } else {
            out.writeByte(mask | nbits);
            int length = i - nbits;
            for (;;) {
                if ((length & ~0x7F) == 0) {
                    out.writeByte(length);
                    return;
                } else {
                    out.writeByte((length & 0x7F) | 0x80);
                    length >>>= 7;
                }
            }
        }
    }

    /**
     * Encode string literal according to Section 5.2.
     */
    private void encodeStringLiteral(ByteBuf out, CharSequence string) {
        int huffmanLength = huffmanEncoder.getEncodedLength(string);
        if ((huffmanLength < string.length() && !forceHuffmanOff) || forceHuffmanOn) {
            encodeInteger(out, 0x80, 7, huffmanLength);
            huffmanEncoder.encode(out, string);
        } else {
            encodeInteger(out, 0x00, 7, string.length());
            if (string instanceof AsciiString) {
                // Fast-path
                AsciiString asciiString = (AsciiString) string;
                out.writeBytes(asciiString.array(), asciiString.arrayOffset(), asciiString.length());
            } else {
                // Only ASCII is allowed in http2 headers, so its fine to use this.
                // https://tools.ietf.org/html/rfc7540#section-8.1.2
                out.writeCharSequence(string, CharsetUtil.ISO_8859_1);
            }
        }
    }

    /**
     * Encode literal header field according to Section 6.2.
     */
    private void encodeLiteral(ByteBuf out, CharSequence name, CharSequence value, HpackUtil.IndexType indexType,
                               int nameIndex) {
        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(CharSequence 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) {
        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(CharSequence name, CharSequence 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(CharSequence 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 -1;
        }
        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(CharSequence name, CharSequence 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();
        }

        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;
        size = 0;
    }

    /**
     * Returns the hash code for the given header field name.
     */
    private static int hash(CharSequence name) {
        int h = 0;
        for (int i = 0; i < name.length(); i++) {
            h = 31 * h + name.charAt(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, CharSequence name, CharSequence 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 references to prevent nepotism in generational GC.
            after = null;
            next = null;
        }

        /**
         * 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;
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java Encoder.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.