content: /*
   * 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.calcite.util;
  
  import org.apache.calcite.linq4j.function.Experimental;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  import java.util.NavigableMap;
  import java.util.stream.Collectors;
  
  import static org.apache.calcite.util.CaseInsensitiveComparator.COMPARATOR;
  
  /** Multimap whose keys are names and can be accessed with and without case
   * sensitivity.
   *
   * @param <V> Value type */
  public class NameMultimap<V> {
    private final NameMap<List<V>> map;
  
    /** Creates a NameMultimap based on an existing map. */
    private NameMultimap(NameMap<List<V>> map) {
      this.map = map;
      assert map.map().comparator() == COMPARATOR;
    }
  
    /** Creates a NameMultimap, initially empty. */
    public NameMultimap() {
      this(new NameMap<>());
    }
  
    @Override public String toString() {
      return map.toString();
    }
  
    @Override public int hashCode() {
      return map.hashCode();
    }
  
    @Override public boolean equals(@Nullable Object obj) {
      return this == obj
          || obj instanceof NameMultimap
          && map.equals(((NameMultimap) obj).map);
    }
  
    /** Adds an entry to this multimap. */
    public void put(String name, V v) {
      List<V> list = map().computeIfAbsent(name, k -> new ArrayList<>());
      list.add(v);
    }
  
    /** Removes all entries that have the given case-sensitive key.
     *
     * @return Whether a value was removed */
    @Experimental
    public boolean remove(String key, V value) {
      final List<V> list = map().get(key);
      if (list == null) {
        return false;
      }
      return list.remove(value);
    }
  
    /** Returns a map containing all the entries in this multimap that match the
     * given name. */
    public Collection<Map.Entry<String, V>> range(String name,
        boolean caseSensitive) {
      NavigableMap<String, List<V>> range = map.range(name, caseSensitive);
      List<Pair<String, V>> result = range.entrySet().stream()
          .flatMap(e -> e.getValue().stream().map(v -> Pair.of(e.getKey(), v)))
          .collect(Collectors.toList());
      return Collections.unmodifiableList(result);
    }
  
    /** Returns whether this map contains a given key, with a given
     * case-sensitivity. */
    public boolean containsKey(String name, boolean caseSensitive) {
      return map.containsKey(name, caseSensitive);
    }
  
    /** Returns the underlying map.
     * Its size is the number of keys, not the number of values. */
    public NavigableMap<String, List<V>> map() {
      return map.map();
    }
  }
  