/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.engine.internal.cache; import com.google.dart.engine.internal.context.InternalAnalysisContext; import com.google.dart.engine.source.Source; import com.google.dart.engine.utilities.collection.MapIterator; import com.google.dart.engine.utilities.collection.SingleMapIterator; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Instances of the class {@code CachePartition} implement a single partition in an LRU cache of * information related to analysis. */ public abstract class CachePartition { /** * The context that owns this partition. Multiple contexts can reference a partition, but only one * context can own it. */ private InternalAnalysisContext context; /** * The maximum number of sources for which AST structures should be kept in the cache. */ private int maxCacheSize; /** * The policy used to determine which pieces of data to remove from the cache. */ private CacheRetentionPolicy retentionPolicy; /** * A table mapping the sources belonging to this partition to the information known about those * sources. */ private final HashMap<Source, SourceEntry> sourceMap = new HashMap<Source, SourceEntry>(); /** * A list containing the most recently accessed sources with the most recently used at the end of * the list. When more sources are added than the maximum allowed then the least recently used * source will be removed and will have it's cached AST structure flushed. */ private ArrayList<Source> recentlyUsed; /** * Initialize a newly created cache to maintain at most the given number of AST structures in the * cache. * * @param context the context that owns this partition * @param maxCacheSize the maximum number of sources for which AST structures should be kept in * the cache * @param retentionPolicy the policy used to determine which pieces of data to remove from the * cache */ public CachePartition(InternalAnalysisContext context, int maxCacheSize, CacheRetentionPolicy retentionPolicy) { this.context = context; this.maxCacheSize = maxCacheSize; this.retentionPolicy = retentionPolicy; recentlyUsed = new ArrayList<Source>(maxCacheSize); } /** * Record that the AST associated with the given source was just read from the cache. * * @param source the source whose AST was accessed */ public void accessedAst(Source source) { if (recentlyUsed.remove(source)) { recentlyUsed.add(source); return; } while (recentlyUsed.size() >= maxCacheSize) { if (!flushAstFromCache()) { break; } } recentlyUsed.add(source); } /** * Return {@code true} if the given source is contained in this partition. * * @param source the source being tested * @return {@code true} if the source is contained in this partition */ public abstract boolean contains(Source source); /** * Return the entry associated with the given source. * * @param source the source whose entry is to be returned * @return the entry associated with the given source */ public SourceEntry get(Source source) { return sourceMap.get(source); } /** * Return the number of entries in this partition that have an AST associated with them. * * @return the number of entries in this partition that have an AST associated with them */ public int getAstSize() { int astSize = 0; int count = recentlyUsed.size(); for (int i = 0; i < count; i++) { Source source = recentlyUsed.get(i); SourceEntry sourceEntry = sourceMap.get(source); if (sourceEntry instanceof DartEntry) { if (((DartEntry) sourceEntry).getAnyParsedCompilationUnit() != null) { astSize++; } } else if (sourceEntry instanceof HtmlEntry) { if (((HtmlEntry) sourceEntry).getAnyParsedUnit() != null) { astSize++; } } } return astSize; } /** * Return the context that owns this partition. * * @return the context that owns this partition */ public InternalAnalysisContext getContext() { return context; } /** * Return a table mapping the sources known to the context to the information known about the * source. * <p> * <b>Note:</b> This method is only visible for use by {@link AnalysisCache} and should not be * used for any other purpose. * * @return a table mapping the sources known to the context to the information known about the * source */ public Map<Source, SourceEntry> getMap() { return sourceMap; } /** * Return an iterator returning all of the map entries mapping sources to cache entries. * * @return an iterator returning all of the map entries mapping sources to cache entries */ public MapIterator<Source, SourceEntry> iterator() { return new SingleMapIterator<Source, SourceEntry>(sourceMap); } /** * Associate the given entry with the given source. * * @param source the source with which the entry is to be associated * @param entry the entry to be associated with the source */ public void put(Source source, SourceEntry entry) { ((SourceEntryImpl) entry).fixExceptionState(); sourceMap.put(source, entry); } /** * Remove all information related to the given source from this cache. * * @param source the source to be removed */ public void remove(Source source) { recentlyUsed.remove(source); sourceMap.remove(source); } /** * Record that the AST associated with the given source was just removed from the cache. * * @param source the source whose AST was removed */ public void removedAst(Source source) { recentlyUsed.remove(source); } /** * Set the maximum size of the cache to the given size. * * @param size the maximum number of sources for which AST structures should be kept in the cache */ public void setMaxCacheSize(int size) { maxCacheSize = size; while (recentlyUsed.size() > maxCacheSize) { if (!flushAstFromCache()) { break; } } } /** * Return the number of sources that are mapped to cache entries. * * @return the number of sources that are mapped to cache entries */ public int size() { return sourceMap.size(); } /** * Record that the AST associated with the given source was just stored to the cache. * * @param source the source whose AST was stored */ public void storedAst(Source source) { if (recentlyUsed.contains(source)) { return; } while (recentlyUsed.size() >= maxCacheSize) { if (!flushAstFromCache()) { break; } } recentlyUsed.add(source); } /** * Attempt to flush one AST structure from the cache. * * @return {@code true} if a structure was flushed */ private boolean flushAstFromCache() { Source removedSource = removeAstToFlush(); if (removedSource == null) { return false; } SourceEntry sourceEntry = sourceMap.get(removedSource); if (sourceEntry instanceof HtmlEntry) { HtmlEntryImpl htmlCopy = ((HtmlEntry) sourceEntry).getWritableCopy(); htmlCopy.flushAstStructures(); sourceMap.put(removedSource, htmlCopy); } else if (sourceEntry instanceof DartEntry) { DartEntryImpl dartCopy = ((DartEntry) sourceEntry).getWritableCopy(); dartCopy.flushAstStructures(); sourceMap.put(removedSource, dartCopy); } return true; } /** * Remove and return one source from the list of recently used sources whose AST structure can be * flushed from the cache. The source that will be returned will be the source that has been * unreferenced for the longest period of time but that is not a priority for analysis. * * @return the source that was removed */ private Source removeAstToFlush() { int sourceToRemove = -1; for (int i = 0; i < recentlyUsed.size(); i++) { Source source = recentlyUsed.get(i); RetentionPriority priority = retentionPolicy.getAstPriority(source, sourceMap.get(source)); if (priority == RetentionPriority.LOW) { return recentlyUsed.remove(i); } else if (priority == RetentionPriority.MEDIUM && sourceToRemove < 0) { sourceToRemove = i; } } if (sourceToRemove < 0) { // This happens if the retention policy returns a priority of HIGH for all of the sources that // have been recently used. This is the case, for example, when the list of priority sources // is bigger than the current cache size. return null; } return recentlyUsed.remove(sourceToRemove); } }