PropertyNameMatcher.java

package tools.jackson.core.sym;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import tools.jackson.core.util.InternCache;
import tools.jackson.core.util.Named;

/**
 * Interface for implementations used for efficient matching of Object property names from
 * input stream (via parser) to higher-level abstractions like properties that
 * databind uses. Used to avoid two-phase lookups -- first from input stream to
 * strings; then from strings to entities -- but details may heavily depend on
 * format parser (some formats can optimize better than others).
 *
 * @since 3.0
 */
public abstract class PropertyNameMatcher
    implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;

    private final static InternCache INTERNER = InternCache.instance;

    /**
     * Marker for case where <code>JsonToken.END_OBJECT</code> encountered.
     */
    public final static int MATCH_END_OBJECT = -1;

    /**
     * Marker for case where property name encountered but not one of matches.
     */
    public final static int MATCH_UNKNOWN_NAME = -2;

    /**
     * Marker for case where token encountered is neither <code>PROPERTY_NAME</code>
     * nor <code>END_OBJECT</code>.
     */
    public final static int MATCH_ODD_TOKEN = -3;

    // // // Original indexed Strings (dense) iff preserved

    protected final String[] _nameLookup;

    // // // Backup index, mostly for case-insensitive lookups

    protected final PropertyNameMatcher _backupMatcher;

    /**
     * Since case-handling is Locale-specific in some (rare) cases, need to hold
     * on to configured Locale.
     */
    protected final Locale _locale;

    /*
    /**********************************************************************
    /* Construction
    /**********************************************************************
     */

    protected PropertyNameMatcher(Locale locale,
            PropertyNameMatcher backup, String[] nameLookup)
    {
        _locale = locale;
        _backupMatcher = backup;
        _nameLookup = nameLookup;
    }

    /*
    /**********************************************************************
    /* API: lookup by String
    /**********************************************************************
     */

    /**
     * Lookup method that does not assume name to be matched to be
     * {@link String#intern}ed (although passing interned String is likely
     * to result in more efficient matching).
     *
     * @param toMatch Name to match
     *
     * @return Index of the name matched, if any (non-negative number); or an
     *    error code (negative constant {@code MATCH_xxx}) if none
     */
    public abstract int matchName(String toMatch);

    /*
    /**********************************************************************
    /* API: lookup by quad-bytes
    /**********************************************************************
     */

    public abstract int matchByQuad(int q1);

    public abstract int matchByQuad(int q1, int q2);

    public abstract int matchByQuad(int q1, int q2, int q3);

    public abstract int matchByQuad(int[] q, int qlen);

    /*
    /**********************************************************************
    /* API: optional access to indexed Strings
    /**********************************************************************
     */

    /**
     * Accessor to names matching indexes, iff passed during construction.
     *
     * @return Array of names that this matcher may match (with indices that
     *   match non-negative values by {@code matchXxx} methods)
     */
    public final String[] nameLookup() {
        return _nameLookup;
    }

    /*
    /**********************************************************************
    /* Methods for sub-classes to implement
    /**********************************************************************
     */

    /**
     * Secondary lookup method used for matchers that operate with more complex
     * matching rules, such as case-insensitive matchers.
     *
     * @param toMatch Name to match
     *
     * @return Index for the match, if any (non-negative); or error code if no match
     */
    protected int matchSecondary(String toMatch) {
        if (_backupMatcher == null) {
            return MATCH_UNKNOWN_NAME;
        }
        // 04-Dec-2017, tatu: Note that we absolutely MUST do another lookup even if
        //   key does not change; thing being that we are now using secondary index,
        //   contents of which MAY be different from primary one. Specifically, if original
        //   keys are not all lower-case, we would induce a miss if skipping lookup here.
        return _backupMatcher.matchName(toMatch.toLowerCase(_locale));
    }

    /*
    /**********************************************************************
    /* Helper methods for sub-classes
    /**********************************************************************
     */

    protected final static int _hash(int h, int mask) {
        // for some reason, slight shuffle with add (not xor!) works quite well
        return (h + (h >> 3)) & mask;
    }

    protected static int _findSize(int size) {
        if (size <= 5) return 8;
        if (size <= 11) return 16;
        if (size <= 23) return 32;
        int needed = size + (size >> 2) + (size >> 4); // at most 75% full
        int result = 64;
        while (result < needed) {
            result += result;
        }
        return result;
    }

    public static List<String> stringsFromNames(List<Named> properties,
            final boolean alreadyInterned)
    {
        // 29-Jan-2018, tatu: With seemingly simple definition (commented out) getting
        //   strange "java.lang.NoClassDefFoundError: Could not initialize class java.util.stream.StreamOpFlag"
        //   so having to replace with bit different
        /*
        return properties.stream()
                .map(n -> _fromName(n, alreadyInterned))
                .collect(Collectors.toList());
                */
        ArrayList<String> result = new ArrayList<String>(properties.size());
        for (Named n : properties) {
            result.add(_fromName(n, alreadyInterned));
        }
        return result;
    }

    protected static String _fromName(Named n, boolean alreadyInterned) {
        if (n == null) return null;
        String name = n.getName();
        return alreadyInterned ? name : INTERNER.intern(name);
    }

    protected static List<String> _lc(Locale locale, List<String> src) {
        List<String> lcd = new ArrayList<>(src.size());
        for (String n : src) {
            lcd.add((n == null) ? null : n.toLowerCase(locale));
        }
        return lcd;
    }
}