TailScopedDictionary.java

/*
** Module   : TailScopedDictionary.java
** Abstract : A ScopedDictionary that contains TailMap collections.
**
** Copyright (c) 2022-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ----------------------------Description----------------------------
** 001 TW  20220617 Initial implementation.
** 002 TW  20220620 Javadoc fix.
** 003 TW  20220727 Turn EmptyTailMap remove implementations into no-op, to fix ScopedDictionary  problem 
**                  identified by CA. Refs #6356-105
** 004 CA  20230223 Avoid the overhead of calculating an empty map when the scope has no dictionary - 
**                  internally, Node.getDictionary(false) will return null if there is no dictionary; but,
**                  getDictionaryAtScope() will still return an empty map, instead of null.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.util;

import java.util.*;
import java.util.function.*;

/**
 * A {@link ScopedDictionary} that contains {@link TailMap} collections.
 * Supported scope: context local.
 * @param   <K>
 *          Dictionary key type
 * @param   <V>
 *          Dictionary value type
 */
public class TailScopedDictionary<K, V>
extends ScopedDictionary<K, V>
{
   /**
    * <code>EMPTY_TAILMAP</code> is a singleton used for {@link #getDictionaryAtScope} when there is no 
    * dictionary at that level.
    */
   @SuppressWarnings("rawtypes")
   private static final TailMap EMPTY_TAILMAP = new EmptyTailMap<>();
   
   /**
    * Default constructor.
    */
   public TailScopedDictionary()
   {
      super();
   }

   /**
    * Returns the dictionary from the scope specified, or <code>null</code> if
    * the specified scope does not exist.
    *
    * @param   scope
    *          The depth (in number of scopes from the top of the stack) at
    *          which the value must be set. Use -1 for the global scope.
    * @param   create
    *          <code>true</code> to create a dictionary for this scope if one
    *          does not already exist;  <code>false</code> to have an empty
    *          (immutable) dictionary returned if one does not already exist.
    * 
    * @return  The dictionary, if any, associated with the specified scope.
    *          Note that this may be an immutable, empty map if no dictionary
    *          exists at this scope, and <code>create</code> is set to
    *          <code>false</code>.  If the scope itself does not exist, then
    *          <code>null</code> is returned.
    */
   @Override
   public TailMap<K, V> getDictionaryAtScope(int scope, boolean create)
   {
      Node<K, V> node = getNodeAt(scope);
      // This cast is safe, since all instances come from our factory methods. 
      TailMap<K, V> dict = null;
      if (node != null)
      {
         dict = (TailMap<K, V>) node.getDictionary(create);
         return dict == null ? EMPTY_TAILMAP : dict; 
      }
      
      return null;
   }
   
   /**
    * Factory method for an {@link IdentityHashMap} 
    *
    * @return  a new {@link TailIdentityHashMap}
    */
   @Override
   protected Map<K, Boolean> factoryIdentityHashMap()
   {
      return new TailIdentityHashMap<>();
   }

   /**
    * Factory method for a {@link Set} as returned by {@link ScopedDictionary#keySet}
    * <p>
    * Currently, the caller can't yet access {@link TailMap#flagIterating}, since a Set
    * is returned. For now, we assume that it's not a realistic scenario in FWD.
    * Redesign when needed (symptom would be a concurrency exception while iterating keySet). 
    *
    * @return The Set returned by <code>Collections.newSetFromMap</code> of a new {@link TailHashMap}
    */
   @Override
   protected Set<K> factoryHashSetFromMap()
   {
      return Collections.newSetFromMap(new TailHashMap<>());
   }

   /**
    * Factory method for an {@link IdentityHashMap} at the scope items (the {@link 
    * ScopedDictionary.Node} inner class)
    *
    * @return  a new {@link TailIdentityHashMap}
    */
   @Override
   protected Map<K, V> factoryNodeIdentityHashMap(int size)
   {
      return new TailIdentityHashMap<>(size);
   }

   /**
    * Factory method for a {@link HashMap} at the scope items (the {@link ScopedDictionary.Node} 
    * inner class)
    *
    * @return  a new {@link TailHashMap}
    */
   @Override
   protected Map<K, V> factoryNodeHashMap(int size)
   {
      return new TailHashMap<>(size);
   }

   /**
    * An immutable empty static subclass of TailHashMap.
    * Attempts to modify this Map result in an <tt>UnsupportedOperationException</tt>.
    * 
    * @param   <K>
    *          Dictionary key type
    * @param   <V>
    *          Dictionary value type
    */
   private static class EmptyTailMap<K, V> 
   extends TailHashMap<K, V>
   {
      /** Generated version UID */
      private static final long serialVersionUID = -4775165838523528350L;
      
      // Note: We can't leverage Collections.unmodifiableMap here, since we prefer to return
      //       a TailMap instance to all client invocations.

      /**
       * Attempts to <code>put</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V put(K key, V value)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * This is a no-op. Attempts to <code>remove</code> will always return <tt>null</tt>.
       */
      @Override
      public V remove(Object key)
      {
         return null;
      }

      /**
       * Attempts to <code>putAll</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public void putAll(Map<? extends K, ? extends V> m)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>replaceAll</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>putIfAbsent</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V putIfAbsent(K key, V value)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * This is a no-op. Attempts to <code>remove</code> will always return <tt>false</tt>.
       */
      @Override
      public boolean remove(Object key, Object value)
      {
         return false;
      }

      /**
       * Attempts to <code>replace</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public boolean replace(K key, V oldValue, V newValue)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>replace</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V replace(K key, V value)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>computeIfAbsent</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>computeIfPresent</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V computeIfPresent(K key,
               BiFunction<? super K, ? super V, ? extends V> remappingFunction)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>compute</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
      {
         throw new UnsupportedOperationException();
      }

      /**
       * Attempts to <code>merge</code> result in an <tt>UnsupportedOperationException</tt>.
       */
      @Override
      public V merge(K key, V value,
               BiFunction<? super V, ? super V, ? extends V> remappingFunction)
      {
         throw new UnsupportedOperationException();
      }

   }
}