SegmentConstantPoolArrayCache.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.harmony.unpack200;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.IdentityHashMap;
  22. import java.util.List;

  23. /**
  24.  * The SegmentConstantPool spends a lot of time searching through large arrays of Strings looking for matches. This can be sped up by caching the arrays in
  25.  * HashMaps so the String keys are looked up and resolve to positions in the array rather than iterating through the arrays each time.
  26.  *
  27.  * Because the arrays only grow (never shrink or change) we can use the last known size as a way to determine if the array has changed.
  28.  *
  29.  * Note that this cache must be synchronized externally if it is shared.
  30.  */
  31. public class SegmentConstantPoolArrayCache {

  32.     /**
  33.      * Keeps track of the last known size of an array as well as a HashMap that knows the mapping from element values to the indices of the array
  34.      * which contain that value.
  35.      */
  36.     protected class CachedArray {
  37.         String[] primaryArray;
  38.         int lastKnownSize;
  39.         HashMap<String, List<Integer>> primaryTable;

  40.         public CachedArray(final String[] array) {
  41.             this.primaryArray = array;
  42.             this.lastKnownSize = array.length;
  43.             this.primaryTable = new HashMap<>(lastKnownSize);
  44.             cacheIndexes();
  45.         }

  46.         /**
  47.          * Given a primaryArray, cache its values in a HashMap to provide a backwards mapping from element values to element indexes. For instance, a
  48.          * primaryArray of: {"Zero", "Foo", "Two", "Foo"} would yield a HashMap of: "Zero" -&gt; 0 "Foo" -&gt; 1, 3 "Two" -&gt; 2 which is then cached.
  49.          */
  50.         protected void cacheIndexes() {
  51.             for (int index = 0; index < primaryArray.length; index++) {
  52.                 final String key = primaryArray[index];
  53.                 primaryTable.computeIfAbsent(key, k -> new ArrayList<>()).add(Integer.valueOf(index));
  54.             }
  55.         }

  56.         /**
  57.          * Given a particular key, answer a List of index locations in the array which contain that key.
  58.          *
  59.          * If no elements are found, answer an empty list.
  60.          *
  61.          * @param key String element of the array
  62.          * @return List of indexes containing that key in the array.
  63.          */
  64.         public List<Integer> indexesForKey(final String key) {
  65.             final List<Integer> list = primaryTable.get(key);
  66.             return list != null ? list : Collections.emptyList();
  67.         }

  68.         /**
  69.          * Answer the last known size of the array cached. If the last known size is not the same as the current size, the array must have changed.
  70.          *
  71.          * @return int last known size of the cached array
  72.          */
  73.         public int lastKnownSize() {
  74.             return lastKnownSize;
  75.         }
  76.     }

  77.     protected IdentityHashMap<String[], CachedArray> knownArrays = new IdentityHashMap<>(1000);
  78.     protected List<Integer> lastIndexes;
  79.     protected String[] lastArray;

  80.     protected String lastKey;

  81.     /**
  82.      * Tests whether a String array is correctly cached. Return false if the array is not cached, or if the array cache is outdated.
  83.      *
  84.      * @param array of String
  85.      * @return boolean true if up-to-date cache, otherwise false.
  86.      */
  87.     protected boolean arrayIsCached(final String[] array) {
  88.         final CachedArray cachedArray = knownArrays.get(array);
  89.         return !(cachedArray == null || cachedArray.lastKnownSize() != array.length);
  90.     }

  91.     /**
  92.      * Caches the array passed in as the argument
  93.      *
  94.      * @param array String[] to cache
  95.      */
  96.     protected void cacheArray(final String[] array) {
  97.         if (arrayIsCached(array)) {
  98.             throw new IllegalArgumentException("Trying to cache an array that already exists");
  99.         }
  100.         knownArrays.put(array, new CachedArray(array));
  101.         // Invalidate the cache-within-a-cache
  102.         lastArray = null;
  103.     }

  104.     /**
  105.      * Gets the indices for the given key in the given array. If no such key exists in the cached array, answer -1.
  106.      *
  107.      * @param array String[] array to search for the value
  108.      * @param key   String value for which to search
  109.      * @return List collection of index positions in the array
  110.      */
  111.     public List<Integer> indexesForArrayKey(final String[] array, final String key) {
  112.         if (!arrayIsCached(array)) {
  113.             cacheArray(array);
  114.         }

  115.         // If the search is one we've just done, don't even
  116.         // bother looking and return the last indices. This
  117.         // is a second cache within the cache. This is
  118.         // efficient because we are usually looking for
  119.         // several secondary elements with the same primary
  120.         // key.
  121.         if (lastArray == array && lastKey == key) {
  122.             return lastIndexes;
  123.         }

  124.         // Remember the last thing we found.
  125.         lastArray = array;
  126.         lastKey = key;
  127.         lastIndexes = knownArrays.get(array).indexesForKey(key);

  128.         return lastIndexes;
  129.     }
  130. }