1 /******************************************************************************* 2 * Copyright (c) 2003, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * Bug 349224 Navigator content provider "appearsBefore" creates hard reference to named id - paul.fullbright@oracle.com 14 * C. Sean Young <csyoung@google.com> - Bug 436645 15 *******************************************************************************/ 16 package org.eclipse.ui.internal.navigator.extensions; 17 18 import java.lang.ref.Reference; 19 import java.lang.ref.ReferenceQueue; 20 import java.util.HashMap; 21 import java.util.Map; 22 23 import org.eclipse.ui.internal.navigator.VisibilityAssistant; 24 import org.eclipse.ui.internal.navigator.VisibilityAssistant.VisibilityListener; 25 26 /** 27 * A cache for evaluated {@link NavigatorContentDescriptor}. 28 */ 29 public class EvaluationCache implements VisibilityListener { 30 // TODO Have an LRU cache with max size as well as SoftReferences, to help 31 // prevent pathological GC performance that can happen where there are a 32 // large number of softly reachable objects, as well as helping to reduce 33 // dependence on the GC from keeping this cache's size in line in the first 34 // place. 35 36 // TODO Counters for cache hits, misses, replacements, etc. 37 38 // TODO Either the overrides and not overrides case should "share" parts of 39 // their data structures (for example, this can be a map of key -> pair 40 // instead of two maps) OR not bother tracking "overrides or not" state here 41 // and instead let users of this class handle it with two instances of this 42 // class. 43 private final Map<EvaluationReference<Object>, EvaluationValueReference<NavigatorContentDescriptor[]>> evaluations = new HashMap<>(); 44 private final Map<EvaluationReference<Object>, EvaluationValueReference<NavigatorContentDescriptor[]>> evaluationsWithOverrides = new HashMap<>(); 45 46 private final ReferenceQueue<Object> evaluationsQueue = new ReferenceQueue<>(); 47 private final ReferenceQueue<Object> evaluationsWithOverridesQueue = new ReferenceQueue<>(); 48 49 /** 50 * @param anAssistant the VisisbilityAssistant to register with, must be non-null 51 */ EvaluationCache(VisibilityAssistant anAssistant)52 public EvaluationCache(VisibilityAssistant anAssistant) { 53 anAssistant.addListener(this); 54 } 55 cleanUpStaleEntries()56 private void cleanUpStaleEntries() { 57 // TODO Only clean up to a certain number of entries per call when merely accessing or setting? 58 // TODO Periodic task to run this every now and then, ala org.eclipse.core.runtime.jobs.Job? 59 // If this is done, will need to make this class thread safe. 60 61 // Not thread safe, but this whole class isn't, so that is fine. 62 Reference<?> r; 63 // Reference#poll thankfully does not block if there is nothing available. 64 while ((r = evaluationsQueue.poll()) != null) { 65 processStaleEntry(r, evaluations); 66 } 67 while ((r = evaluationsWithOverridesQueue.poll()) != null) { 68 processStaleEntry(r, evaluationsWithOverrides); 69 } 70 } 71 processStaleEntry(Reference<?> r, Map<? extends Reference<?>, ? extends Reference<?>> fromMap)72 private static void processStaleEntry(Reference<?> r, 73 Map<? extends Reference<?>, ? extends Reference<?>> fromMap) { 74 if (r instanceof EvaluationReference) { 75 // Key has been collected; clear its entry. 76 EvaluationValueReference<?> oldVal = (EvaluationValueReference<?>) fromMap.remove(r); 77 if (oldVal != null) { 78 // Clear the key from the value so we don't try to prematurely 79 // remove any potential new mapping upon cleanUpStaleEntries() 80 oldVal.clear(); 81 } 82 } 83 if (r instanceof EvaluationValueReference) { 84 // If the value has been collected, get its key, and then remove that entry. 85 EvaluationReference<?> key = ((EvaluationValueReference<?>) r).getKey(); 86 if (key != null) { 87 fromMap.remove(key); 88 } 89 } 90 // All other Reference types we just leave alone. 91 } 92 getDescriptorsFromMap(Object anElement, Map<EvaluationReference<Object>, EvaluationValueReference<NavigatorContentDescriptor[]>> map)93 private static NavigatorContentDescriptor[] getDescriptorsFromMap(Object anElement, 94 Map<EvaluationReference<Object>, EvaluationValueReference<NavigatorContentDescriptor[]>> map) { 95 // Need to wrap in the reference type before querying, else it won't be found by HashMap. 96 EvaluationReference<Object> key = new EvaluationReference<>(anElement); 97 NavigatorContentDescriptor[] cachedDescriptors = null; 98 Reference<NavigatorContentDescriptor[]> cache = map.get(key); 99 if (cache != null && (cachedDescriptors = cache.get()) == null) { 100 // There was an entry, but it has been collected; remove stale mapping. 101 EvaluationValueReference<NavigatorContentDescriptor[]> value = map.remove(key); 102 if (value != null) { 103 // Clear the key from the value so we don't try to prematurely remove any potential new mapping upon cleanUpStaleEntries() 104 value.clear(); 105 } 106 } 107 return cachedDescriptors; 108 } 109 110 /** 111 * Finds the cached descriptors for the given key, or returns {@code null} 112 * if not currently in the cache. 113 * 114 * @param anElement 115 * the key to lookup 116 * @param toComputeOverrides 117 * whether overrides are to be considered 118 * @return the cached descriptors for the given key, or {@code null} if not 119 * currently in the cache 120 */ getDescriptors(Object anElement, boolean toComputeOverrides)121 public final NavigatorContentDescriptor[] getDescriptors(Object anElement, boolean toComputeOverrides) { 122 cleanUpStaleEntries(); 123 if (anElement == null) 124 return null; 125 126 if (toComputeOverrides) { 127 return getDescriptorsFromMap(anElement, evaluations); 128 } 129 return getDescriptorsFromMap(anElement, evaluationsWithOverrides); 130 } 131 setDescriptorsInMap(Object anElement, NavigatorContentDescriptor[] theDescriptors, Map<EvaluationReference<Object>, EvaluationValueReference<NavigatorContentDescriptor[]>> map, ReferenceQueue<Object> queue)132 private static void setDescriptorsInMap(Object anElement, NavigatorContentDescriptor[] theDescriptors, 133 Map<EvaluationReference<Object>, EvaluationValueReference<NavigatorContentDescriptor[]>> map, 134 ReferenceQueue<Object> queue) { 135 // Ideally, we would use a WeakReference wrapper if the object given uses identity equality 136 // (we can test if the class uses Object's equals or has its own override), and only use a SoftReference 137 // if the object overrides equals, but that is a bit too unwieldy to check (it would require 138 // checking reflective data) to be worth it. 139 EvaluationReference<Object> key = new EvaluationReference<>(anElement, queue); 140 EvaluationValueReference<NavigatorContentDescriptor[]> newValue = 141 new EvaluationValueReference<>(theDescriptors, key, queue); 142 EvaluationValueReference<NavigatorContentDescriptor[]> oldValue = map.put(key, newValue); 143 if (oldValue != null) { 144 // "Swap" the correct key instance when swapping the value, or else the above, temporary 145 // lookup key will be collected too early (not the actual anElement, but the Reference object). 146 // Not truly needed, but it will help the point of this field not go to waste. 147 newValue.swapKey(oldValue); 148 // Clear the key so we don't try to prematurely remove the new mapping upon cleanUpStaleEntries() 149 oldValue.clear(); 150 } 151 } 152 153 /** 154 * Caches the given descriptors with the given key. 155 * 156 * @param anElement 157 * the key to associate with the given descriptors 158 * @param theDescriptors 159 * the descriptors to cache against the given key 160 * @param toComputeOverrides 161 * whether overrides were considered in the computation of the 162 * given descriptors 163 */ setDescriptors(Object anElement, NavigatorContentDescriptor[] theDescriptors, boolean toComputeOverrides)164 public final void setDescriptors(Object anElement, NavigatorContentDescriptor[] theDescriptors, 165 boolean toComputeOverrides) { 166 cleanUpStaleEntries(); 167 if (anElement != null) { 168 if (toComputeOverrides) { 169 setDescriptorsInMap(anElement, theDescriptors, evaluations, evaluationsQueue); 170 } else { 171 setDescriptorsInMap(anElement, theDescriptors, evaluationsWithOverrides, evaluationsWithOverridesQueue); 172 } 173 } 174 } 175 176 /** 177 * {@inheritDoc} 178 * 179 * For an EvaluationCache, this means invalidating all cached descriptors. 180 */ 181 @Override onVisibilityOrActivationChange()182 public void onVisibilityOrActivationChange() { 183 clear(); 184 } 185 186 /** 187 * Clears the cache. 188 */ clear()189 public void clear() { 190 // Dump everything in the reference queues. 191 // Don't bother removing from the map based on references, we are about to clear everything anyways. 192 // This might lead to some premature removals because yet to be collected values are not clearing 193 // their key reference, but that is worth having a fast clearing of the maps. 194 // Thankfully, this should be rare, as when reference objects themselves are GCed before being added 195 // to a reference queue, they don't get added at all. 196 while (evaluationsQueue.poll() != null) { 197 // No need to do anything with the reference, we just need to drain 198 // the queue. 199 } 200 while (evaluationsWithOverridesQueue.poll() != null) { 201 // No need to do anything with the reference, we just need to drain 202 // the queue. 203 } 204 evaluations.clear(); 205 evaluationsWithOverrides.clear(); 206 } 207 }