All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.vertx.http.runtime.security.ImmutableSubstringMap Maven / Gradle / Ivy

package io.quarkus.vertx.http.runtime.security;

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

import io.quarkus.vertx.http.runtime.security.ImmutablePathMatcher.PathMatch;

/**
 * A string keyed map that can be accessed as a substring, eliminating the need to allocate a new string
 * to do a key comparison against.
 */
public class ImmutableSubstringMap {

    private static final int ALL_BUT_LAST_BIT = ~1;
    private final Object[] table;

    ImmutableSubstringMap(Object[] table) {
        this.table = Arrays.copyOf(table, table.length);
    }

    @SuppressWarnings("unchecked")
    public SubstringMatch get(String key, int length) {
        if (key.length() < length) {
            throw new IllegalArgumentException();
        }
        int hash = hash(key, length);
        int pos = tablePos(table, hash);
        int start = pos;
        while (table[pos] != null) {
            if (doEquals((String) table[pos], key, length)) {
                SubstringMatch match = (SubstringMatch) table[pos + 1];
                if (match == null) {
                    return null;
                }
                if (match.hasSubPathMatcher) {
                    // consider request path '/one/two/three/four/five'
                    // 'match.key' (which is prefix path) never ends with a slash, e.g. 'match.key=/one/two'
                    // which means index 'match.key.length()' is index of the last char of the '/one/two/' sub-path
                    // considering we are looking for a path segment after '/one/two/*', that is the first char
                    // of the '/four/five' sub-path, the separator index must be greater than 'match.key.length() + 1'
                    if (key.length() > (match.key.length() + 1)) {
                        // let say match key is '/one/two'
                        // then next path segment is '/four' and '/three' is skipped
                        // for path pattern was like: '/one/two/*/four/five'
                        int nextPathSegmentIdx = key.indexOf('/', match.key.length() + 1);
                        if (nextPathSegmentIdx != -1) {
                            // following the example above, 'nextPath' would be '/four/five'
                            // and * matched 'three' path segment characters
                            String nextPath = key.substring(nextPathSegmentIdx);
                            PathMatch> subMatch = match.subPathMatcher.match(nextPath);
                            if (subMatch.getValue() != null) {
                                return subMatch.getValue();
                            }
                        }
                    }

                    if (match.value == null) {
                        // paths with inner wildcard didn't match
                        // and there is no prefix path with ending wildcard either
                        return null;
                    }
                }
                // prefix path with ending wildcard: /one/two*
                return match;
            }
            pos += 2;
            if (pos >= table.length) {
                pos = 0;
            }
            if (pos == start) {
                return null;
            }
        }
        return null;
    }

    static int tablePos(Object[] table, int hash) {
        return (hash & (table.length - 1)) & ALL_BUT_LAST_BIT;
    }

    static boolean doEquals(String s1, String s2, int length) {
        if (s1.length() != length || s2.length() < length) {
            return false;
        }
        for (int i = 0; i < length; ++i) {
            if (s1.charAt(i) != s2.charAt(i)) {
                return false;
            }
        }
        return true;
    }

    static int hash(String value, int length) {
        if (length == 0) {
            return 0;
        }
        int h = 0;
        for (int i = 0; i < length; i++) {
            h = 31 * h + value.charAt(i);
        }
        return h;
    }

    public static final class SubstringMatch {
        private final String key;
        private final V value;
        private final boolean hasSubPathMatcher;
        private final ImmutablePathMatcher> subPathMatcher;

        SubstringMatch(String key, V value) {
            this.key = key;
            this.value = value;
            this.subPathMatcher = null;
            this.hasSubPathMatcher = false;
        }

        SubstringMatch(String key, V value, ImmutablePathMatcher> subPathMatcher) {
            this.key = key;
            this.value = value;
            this.subPathMatcher = subPathMatcher;
            this.hasSubPathMatcher = subPathMatcher != null;
        }

        public String getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        boolean hasSubPathMatcher() {
            return hasSubPathMatcher;
        }
    }

    static  SubstringMapBuilder builder() {
        return new SubstringMapBuilder<>();
    }

    static final class SubstringMapBuilder {
        private Object[] table = new Object[16];
        private int size;

        private SubstringMapBuilder() {
        }

        void put(String key, V value, ImmutablePathMatcher> subPathMatcher) {
            if (key == null) {
                throw new NullPointerException();
            }

            Object[] newTable;
            if (table.length / (double) size < 4 && table.length != Integer.MAX_VALUE) {
                newTable = new Object[table.length << 1];
                for (int i = 0; i < table.length; i += 2) {
                    if (table[i] != null) {
                        doPut(newTable, (String) table[i], table[i + 1]);
                    }
                }
            } else {
                newTable = new Object[table.length];
                System.arraycopy(table, 0, newTable, 0, table.length);
            }
            doPut(newTable, key, new SubstringMatch<>(key, value, subPathMatcher));
            this.table = newTable;
            size++;
        }

        private void doPut(Object[] newTable, String key, Object value) {
            int hash = hash(key, key.length());
            int pos = tablePos(newTable, hash);
            while (newTable[pos] != null && !newTable[pos].equals(key)) {
                pos += 2;
                if (pos >= newTable.length) {
                    pos = 0;
                }
            }
            newTable[pos] = key;
            newTable[pos + 1] = value;
        }

        public Iterable keys() {
            return new Iterable() {
                @Override
                public Iterator iterator() {
                    final Object[] tMap = table;
                    int i = 0;
                    while (i < table.length && tMap[i] == null) {
                        i += 2;
                    }
                    final int startPos = i;

                    return new Iterator() {

                        private Object[] map = tMap;

                        private int pos = startPos;

                        @Override
                        public boolean hasNext() {
                            return pos < table.length;
                        }

                        @Override
                        public String next() {
                            if (!hasNext()) {
                                throw new NoSuchElementException();
                            }
                            String ret = (String) map[pos];

                            pos += 2;
                            while (pos < table.length && tMap[pos] == null) {
                                pos += 2;
                            }
                            return ret;
                        }

                        @Override
                        public void remove() {
                            throw new UnsupportedOperationException();
                        }
                    };
                }
            };

        }

        ImmutableSubstringMap build() {
            return new ImmutableSubstringMap<>(table);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy