001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import java.util.*;
016import java.util.concurrent.*;
017import java.util.function.*;
018
019/**
020 * A hashmap that allows for two-part keys.
021 *
022 * <h5 class='section'>See Also:</h5><ul>
023 * </ul>
024 *
025 * @param <K1> Key part 1 type.
026 * @param <K2> Key part 2 type.
027 * @param <V> Value type.
028 * @serial exclude
029 */
030public class TwoKeyConcurrentCache<K1,K2,V> extends ConcurrentHashMap<TwoKeyConcurrentCache.Key<K1,K2>,V> {
031   private static final long serialVersionUID = 1L;
032
033   private final boolean disabled;
034   private final BiFunction<K1,K2,V> supplier;
035
036   /**
037    * Constructor.
038    */
039   public TwoKeyConcurrentCache() {
040      this(false, null);
041   }
042
043   /**
044    * Constructor.
045    * @param disabled If <jk>true</jk>, get/put operations are no-ops.
046    * @param supplier The supplier for this cache.
047    */
048   public TwoKeyConcurrentCache(boolean disabled, BiFunction<K1,K2,V> supplier) {
049      this.disabled = disabled;
050      this.supplier = supplier;
051   }
052
053   /**
054    * Adds an entry to this map.
055    *
056    * @param key1 Key part 1.  Can be <jk>null</jk>.
057    * @param key2 Key part 2.  Can be <jk>null</jk>.
058    * @param value Value.
059    * @return The previous value if there was one.
060    */
061   public V put(K1 key1, K2 key2, V value) {
062      if (disabled)
063         return null;
064      Key<K1,K2> key = new Key<>(key1, key2);
065      return super.put(key, value);
066   }
067
068   /**
069    * Retrieves an entry from this map.
070    *
071    * @param key1 Key part 1.  Can be <jk>null</jk>.
072    * @param key2 Key part 2.  Can be <jk>null</jk>.
073    * @return The previous value if there was one.
074    */
075   public V get(K1 key1, K2 key2) {
076      if (disabled)
077         return (supplier == null ? null : supplier.apply(key1, key2));
078      Key<K1,K2> key = new Key<>(key1, key2);
079      V v = super.get(key);
080      if (v == null && supplier != null) {
081         v = supplier.apply(key1, key2);
082         super.put(key, v);
083      }
084      return v;
085   }
086
087   static class Key<K1,K2> {
088      final K1 k1;
089      final K2 k2;
090      final int hashCode;
091
092      Key(K1 k1, K2 k2) {
093         this.k1 = k1;
094         this.k2 = k2;
095         this.hashCode = 31*(k1 == null ? 0 : k1.hashCode()) + (k2 == null ? 0 : k2.hashCode());
096      }
097
098      @Override /* Object */
099      public int hashCode() {
100         return hashCode;
101      }
102
103      @Override /* Object */
104      @SuppressWarnings("unchecked")
105      public boolean equals(Object o) {
106         Key<K1,K2> ko = (Key<K1,K2>)o;
107         return Objects.equals(k1, ko.k1) && Objects.equals(k2, ko.k2);
108      }
109   }
110}