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

com.google.appengine.api.datastore.PrefixTrie Maven / Gradle / Ivy

Go to download

API for Google App Engine standard environment with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.google.appengine.api.datastore;

import com.google.common.collect.Maps;
import java.util.Map;

/**
 * Trie implementation supporting CharSequences as prefixes.
 *
 * 

Prefixes are sequences of characters, and the set of allowed characters is specified as a * range of sequential characters. By default, any seven-bit character may appear in a prefix, and * so the trie is a 128-ary tree. * * @param the type of values in the trie. */ class PrefixTrie { /* * The set of allowed characters in prefixes is given by a range of * consecutive characters. rangeOffset denotes the beginning of the range, * and rangeSize gives the number of characters in the range, which is used as * the number of children of each node. */ private final char rangeOffset; private final int rangeSize; private final Node root; /** Constructs a trie for holding strings of seven-bit characters. */ PrefixTrie() { rangeOffset = '\0'; rangeSize = 128; root = new Node(rangeSize); } /** * Constructs a trie for holding strings of characters. The set of characters allowed in prefixes * is given by the range [rangeOffset, lastCharInRange], inclusive. */ PrefixTrie(char firstCharInRange, char lastCharInRange) { this.rangeOffset = firstCharInRange; this.rangeSize = lastCharInRange - firstCharInRange + 1; if (rangeSize <= 0) { throw new IllegalArgumentException("Char range must include some chars"); } root = new Node(rangeSize); } /** * Maps prefix to value, which must not be null. * * @return the previous value stored for this prefix, or {@code null} if none * @throws IllegalArgumentException if prefix is an empty string, or contains a character outside * the range of legal prefix characters. */ T put(CharSequence prefix, T value) { if (value == null) { throw new NullPointerException(); } return putInternal(prefix, value); } private T putInternal(CharSequence prefix, T value) { Node current = root; for (int i = 0; i < prefix.length(); i++) { int nodeIndex = prefix.charAt(i) - rangeOffset; try { Node next = current.next[nodeIndex]; if (next == null) { next = current.next[nodeIndex] = new Node(rangeSize); } current = next; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException( "'" + prefix.charAt(i) + "' is not a legal prefix character."); } } T oldValue = current.value; current.value = value; return oldValue; } /** * Finds a prefix that matches {@code s} and returns the mapped value. If multiple prefixes in the * map match {@code s}, the longest match wins. * * @return value for prefix matching {@code s}, or {@code null} if none match */ T get(CharSequence s) { Node deepestWithValue = root; Node current = root; for (int i = 0; i < s.length(); i++) { int nodeIndex = s.charAt(i) - rangeOffset; if (nodeIndex < 0 || rangeSize <= nodeIndex) { return null; } current = current.next[nodeIndex]; if (current == null) { break; } if (current.value != null) { deepestWithValue = current; } } return deepestWithValue.value; } /** * Removes an exact prefix stored in the map, returning the old value, or null if it did not * exist. Inverse operation of put(prefix). * * @return the previous value stored for this prefix, or {@code null} if none * @throws IllegalArgumentException if prefix is an empty string, or contains a character outside * the range of legal prefix characters. */ T remove(CharSequence prefix) { return putInternal(prefix, null); } /** * Returns a Map containing the same data as this structure. * *

This implementation constructs and populates an entirely new map rather than providing a map * view on the trie, so this is mostly useful for debugging. * * @return a Map mapping each prefix to its corresponding value. */ Map toMap() { Map map = Maps.newLinkedHashMap(); addEntries(root, new StringBuilder(), map); return map; } /** * Adds to the given map all entries at or below the given node. * * @param builder a StringBuilder containing the prefix for the given node. */ private void addEntries(Node node, StringBuilder builder, Map map) { if (node.value != null) { map.put(builder.toString(), node.value); } for (int i = 0; i < node.next.length; i++) { Node next = node.next[i]; if (next != null) { builder.append((char) (i + rangeOffset)); addEntries(next, builder, map); builder.deleteCharAt(builder.length() - 1); } } } private static class Node { T value; final Node[] next; @SuppressWarnings({"rawtypes", "unchecked"}) Node(int numChildren) { next = new Node[numChildren]; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy