001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.compress.harmony.unpack200; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.IdentityHashMap; 023import java.util.List; 024 025/** 026 * The SegmentConstantPool spends a lot of time searching through large arrays of Strings looking for matches. This can 027 * be sped up by caching the arrays in HashMaps so the String keys are looked up and resolve to positions in the array 028 * rather than iterating through the arrays each time. 029 * 030 * Because the arrays only grow (never shrink or change) we can use the last known size as a way to determine if the 031 * array has changed. 032 * 033 * Note that this cache must be synchronized externally if it is shared. 034 */ 035public class SegmentConstantPoolArrayCache { 036 037 /** 038 * CachedArray keeps track of the last known size of an array as well as a HashMap that knows the mapping from 039 * element values to the indices of the array which contain that value. 040 */ 041 protected class CachedArray { 042 String[] primaryArray; 043 int lastKnownSize; 044 HashMap<String, List<Integer>> primaryTable; 045 046 public CachedArray(final String[] array) { 047 this.primaryArray = array; 048 this.lastKnownSize = array.length; 049 this.primaryTable = new HashMap<>(lastKnownSize); 050 cacheIndexes(); 051 } 052 053 /** 054 * Given a primaryArray, cache its values in a HashMap to provide a backwards mapping from element values to 055 * element indexes. For instance, a primaryArray of: {"Zero", "Foo", "Two", "Foo"} would yield a HashMap of: 056 * "Zero" -> 0 "Foo" -> 1, 3 "Two" -> 2 which is then cached. 057 */ 058 protected void cacheIndexes() { 059 for (int index = 0; index < primaryArray.length; index++) { 060 final String key = primaryArray[index]; 061 primaryTable.computeIfAbsent(key, k -> new ArrayList<>()); 062 primaryTable.get(key).add(Integer.valueOf(index)); 063 } 064 } 065 066 /** 067 * Given a particular key, answer a List of index locations in the array which contain that key. 068 * 069 * If no elements are found, answer an empty list. 070 * 071 * @param key String element of the array 072 * @return List of indexes containing that key in the array. 073 */ 074 public List<Integer> indexesForKey(final String key) { 075 final List<Integer> list = primaryTable.get(key); 076 return list != null ? list : Collections.emptyList(); 077 } 078 079 /** 080 * Answer the last known size of the array cached. If the last known size is not the same as the current size, 081 * the array must have changed. 082 * 083 * @return int last known size of the cached array 084 */ 085 public int lastKnownSize() { 086 return lastKnownSize; 087 } 088 } 089 090 protected IdentityHashMap<String[], CachedArray> knownArrays = new IdentityHashMap<>(1000); 091 protected List<Integer> lastIndexes; 092 protected String[] lastArray; 093 094 protected String lastKey; 095 096 /** 097 * Given a String array, answer true if the array is correctly cached. Answer false if the array is not cached, or 098 * if the array cache is outdated. 099 * 100 * @param array of String 101 * @return boolean true if up-to-date cache, otherwise false. 102 */ 103 protected boolean arrayIsCached(final String[] array) { 104 final CachedArray cachedArray = knownArrays.get(array); 105 return !(cachedArray == null || cachedArray.lastKnownSize() != array.length); 106 } 107 108 /** 109 * Cache the array passed in as the argument 110 * 111 * @param array String[] to cache 112 */ 113 protected void cacheArray(final String[] array) { 114 if (arrayIsCached(array)) { 115 throw new IllegalArgumentException("Trying to cache an array that already exists"); 116 } 117 knownArrays.put(array, new CachedArray(array)); 118 // Invalidate the cache-within-a-cache 119 lastArray = null; 120 } 121 122 /** 123 * Answer the indices for the given key in the given array. If no such key exists in the cached array, answer -1. 124 * 125 * @param array String[] array to search for the value 126 * @param key String value for which to search 127 * @return List collection of index positions in the array 128 */ 129 public List<Integer> indexesForArrayKey(final String[] array, final String key) { 130 if (!arrayIsCached(array)) { 131 cacheArray(array); 132 } 133 134 // If the search is one we've just done, don't even 135 // bother looking and return the last indices. This 136 // is a second cache within the cache. This is 137 // efficient because we usually are looking for 138 // several secondary elements with the same primary 139 // key. 140 if ((lastArray == array) && (lastKey == key)) { 141 return lastIndexes; 142 } 143 144 // Remember the last thing we found. 145 lastArray = array; 146 lastKey = key; 147 lastIndexes = knownArrays.get(array).indexesForKey(key); 148 149 return lastIndexes; 150 } 151}