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

org.apache.solr.common.util.PathTrie Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */

package org.apache.solr.common.util;

import static java.util.Collections.emptyList;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A utility class to efficiently parse/store/lookup hierarchical paths which are templatized like
 * /collections/{collection}/shards/{shard}/{replica}
 */
public class PathTrie {
  private final Set reserved = new HashSet<>();
  Node root = new Node(emptyList(), null, null);

  public PathTrie() {}

  public PathTrie(Set reserved) {
    this.reserved.addAll(reserved);
  }

  public void insert(String path, Map replacements, T o) {
    List parts = getPathSegments(path);
    insert(parts, replacements, o);
  }

  public void insert(List parts, Map replacements, T o) {
    if (parts.isEmpty()) {
      attachValueToNode(root, o);
      return;
    }
    replaceTemplates(parts, replacements);
    root.insert(parts, o);
  }

  public static void replaceTemplates(List parts, Map replacements) {
    for (int i = 0; i < parts.size(); i++) {
      String part = parts.get(i);
      if (part.charAt(0) == '$') {
        String replacement = replacements.get(part.substring(1));
        if (replacement == null) {
          throw new RuntimeException(part + " is not provided");
        }
        replacement = replacement.charAt(0) == '/' ? replacement.substring(1) : replacement;
        parts.set(i, replacement);
      }
    }
  }

  // /a/b/c will be returned as ["a","b","c"]
  public static List getPathSegments(String path) {
    if (path == null || path.isEmpty()) return emptyList();
    List parts =
        new ArrayList<>() {
          @Override
          public boolean add(String s) {
            if (s == null || s.isEmpty()) return false;
            return super.add(s);
          }
        };
    StrUtils.splitSmart(path, '/', parts);
    return parts;
  }

  public T remove(List path) {
    Node node = root.lookupNode(path, 0, null, null);
    T result = null;
    if (node != null) {
      result = node.obj;
      node.obj = null;
      if (node.children == null || node.children.isEmpty()) {
        if (node.parent != null) {
          node.parent.children.remove(node.name);
        }
      }
      return result;
    }
    return result;
  }

  public T lookup(String path, Map templateValues) {
    return root.lookup(getPathSegments(path), 0, templateValues);
  }

  public T lookup(List path, Map templateValues) {
    return root.lookup(path, 0, templateValues);
  }

  public T lookup(String path, Map templateValues, Set paths) {
    return root.lookup(getPathSegments(path), 0, templateValues, paths);
  }

  public static String templateName(String templateStr) {
    return templateStr.startsWith("{") && templateStr.endsWith("}")
        ? templateStr.substring(1, templateStr.length() - 1)
        : null;
  }

  /**
   * Attaches the provided object to the PathTrie node
   *
   * 

The default implementation overwrites any existing values, but this can be overwritten by * subclasses. */ protected void attachValueToNode(PathTrie.Node node, T o) { node.obj = o; } public class Node { String name; Map children; T obj; String templateName; final Node parent; Node(List path, T o, Node parent) { this.parent = parent; if (path.isEmpty()) { obj = o; return; } String part = path.get(0); templateName = templateName(part); name = part; if (path.isEmpty()) obj = o; } public T getObject() { return obj; } public void setObject(T o) { obj = o; } private synchronized void insert(List path, T o) { String part = path.get(0); Node matchedChild = null; if ("*".equals(name)) { return; } if (children == null) children = new ConcurrentHashMap<>(); String varName = templateName(part); String key = varName == null ? part : ""; matchedChild = children.get(key); if (matchedChild == null) { children.put(key, matchedChild = new Node(path, o, this)); } if (varName != null) { if (!matchedChild.templateName.equals(varName)) { throw new RuntimeException("wildcard name must be " + matchedChild.templateName); } } path.remove(0); if (!path.isEmpty()) { matchedChild.insert(path, o); } else { attachValueToNode(matchedChild, o); } } void findAvailableChildren(String path, Set availableSubPaths) { if (availableSubPaths == null) return; if (children != null) { for (Node node : children.values()) { if (node.obj != null) { String s = path + "/" + node.name; availableSubPaths.add(s); } } for (Node node : children.values()) { node.findAvailableChildren(path + "/" + node.name, availableSubPaths); } } } public T lookup(List pieces, int i, Map templateValues) { return lookup(pieces, i, templateValues, null); } /** * @param pathSegments pieces in the url /a/b/c has pieces as 'a' , 'b' , 'c' * @param index current index of the pieces that we are looking at in /a/b/c 0='a' and 1='b' * @param templateVariables The mapping of template variable to its value * @param availableSubPaths If not null , available sub paths will be returned in this set */ public T lookup( List pathSegments, int index, Map templateVariables, Set availableSubPaths) { Node node = lookupNode(pathSegments, index, templateVariables, availableSubPaths); return node == null ? null : node.obj; } Node lookupNode( List pathSegments, int index, Map templateVariables, Set availableSubPaths) { if (templateName != null && templateVariables != null) templateVariables.put(templateName, pathSegments.get(index - 1)); if (pathSegments.size() < index + 1) { findAvailableChildren("", availableSubPaths); if (obj == null) { // this is not a leaf node Node n = children.get("*"); if (n != null) { return n; } } return this; } String piece = pathSegments.get(index); if (children == null) { return null; } Node n = children.get(piece); if (n == null && !reserved.contains(piece)) n = children.get(""); if (n == null) { n = children.get("*"); if (n != null) { StringBuilder sb = new StringBuilder(); for (int i = index; i < pathSegments.size(); i++) { sb.append("/").append(pathSegments.get(i)); } if (templateVariables != null) templateVariables.put("*", sb.toString()); return n; } } if (n == null) { return null; } return n.lookupNode(pathSegments, index + 1, templateVariables, availableSubPaths); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy