/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.apache.flex.compiler.internal.scopes; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.IInterfaceDefinition; import org.apache.flex.compiler.definitions.INamespaceDefinition; import org.apache.flex.compiler.definitions.references.IResolvedQualifiersReference; import org.apache.flex.compiler.internal.definitions.AmbiguousDefinition; import org.apache.flex.compiler.internal.definitions.ClassDefinitionBase; import org.apache.flex.compiler.internal.definitions.ConstantDefinition; import org.apache.flex.compiler.internal.definitions.TypeDefinitionBase; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.projects.ICompilerProject; import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; import java.lang.ref.SoftReference; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentMap; /** * Class to manage cached lookups in a given project. Each scope object will * have one of these per project the scope object is used in. This class is * intended to provide thread safe access to the various caches it maintains. * The caches will be maintained via SoftReferences - this is meant to make the * caches stick around unless the VM really needs the memory back. The caches * can be rebuilt on the fly, so in a worst case scenario where the VM is * constantly low on memory, the results should be correct, but performance will * be slower. */ public class ASScopeCache { /** * The concurrency level to use for the various ConcurrentMaps in use by * this cache. If the concurrency level is too high, you waste time and * space, if it is too low, then you have contention on updates of the map. * 4 seems to be a good tradeoff performance wise, and it is unlikely that * any individual scope cache is being updated from more than 4 threads * simultaneously. If our threading strategy changes (like if we make the CG * multithreaded within one file), we may want to revisit this setting. */ private static final int CONCURRENCY_LEVEL = 4; private static final MapMaker mapMaker = new MapMaker() .concurrencyLevel(CONCURRENCY_LEVEL); public ASScopeCache(CompilerProject project, ASScope scope) { this.scope = scope; this.project = project; } private final ASScope scope; private final CompilerProject project; /** * Cache results of unqualified lookups over the scope chain * (ASScopeBase.findProperty). This is for caching the results of * ASScope.findProperty(). */ private SoftReference<ConcurrentMap<String, IDefinition>> findPropCache; /** * Cache results of lookups of qualified names over the scope chain * (ASScopeBase.findPropertyQualified). */ private SoftReference<ConcurrentMap<QName, IDefinition>> findPropQualifiedCache; /** * Cache the set of open namespaces */ private SoftReference<Set<INamespaceDefinition>> openNamespaceCache = null; /** * Cache the open namespace set per name */ private SoftReference<ConcurrentMap<String, Set<INamespaceDefinition>>> namespacesForNameCache; private SoftReference<ConcurrentMap<IResolvedQualifiersReference, IDefinition>> multinameLookupCache; /** * Cache the compile time values of constants */ private SoftReference<ConcurrentMap<IDefinition, Object>> constValueLookupCache; /** * Cache the needs Event dispatch flag */ private Boolean needsEventDispatcherCache; /** * Cache the extended or implemented interfaces of an interface or class. */ private SoftReference<IInterfaceDefinition[]> interfacesCache; /** * Cache the builtin types we've already added dependencies on */ private SoftReference<Set<IASLanguageConstants.BuiltinType>> builtinTypeDependencyCache; /** * Version of findProperty that uses a cache. Checks the cache first, and * only queries the scope if the we don't have a cached result. * * @param scope The scope to perform the lookup in * @param name Name of the property to find * @param cache ASDefinitionCache to use for the lookup - this is only used * to get at the ICompilerProject * @param dt Which type of dependency to introduce when we do the lookup * @return The IDefinition for the property, or null if it wasn't found */ IDefinition findProperty(String name, DependencyType dt) { ConcurrentMap<String, IDefinition> map = getScopeChainMap(); IDefinition result = map.get(name); if (result != null) { assert result.isInProject(project); // We found a cached result - we're done return result; } // It is possible for 2+ threads to get in here for the same name. // This is intentional - the worst that happens is that we duplicate the resolution work // the benefit is that we avoid any sort of locking, which was proving expensive (time wise, // and memory wise). IDefinition def = null; Set<INamespaceDefinition> namespaceSet = scope.getNamespaceSetForName(project, name); // Look for the definition in the scope List<IDefinition> defs = scope.findProperty(project, name, namespaceSet, dt); switch (defs.size()) { case 0: // No definition found! def = null; break; case 1: // found single definition! def = defs.get(0); assert def.isInProject(project); break; default: IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs); if (d != null) def = d; else { if (defs.size() == 2) { def = project.doubleCheckAmbiguousDefinition(scope, name, defs.get(0), defs.get(1)); if (def != null) return def; } def = AmbiguousDefinition.get(); } } if (def != null) { assert def.isInProject(project); assert result == null; // If we have a non-null dependency type // then we can cache the result of the name resolution. // If the dependency type is null we can't cache the name // resolution result, because the name resolution cache will not // be properly invalidated when the file containing the definition changes. if (dt != null) { result = map.putIfAbsent(name, def); if (result == null) result = def; } else { result = def; } } return result; } private ConcurrentMap<String, IDefinition> getScopeChainMap() { ConcurrentMap<String, IDefinition> map = findPropCache != null ? findPropCache.get() : null; if (map == null) { synchronized (this) { // Check again, in case another thread updated the map first map = findPropCache != null ? findPropCache.get() : null; if (map == null) { map = mapMaker.<String, IDefinition> makeMap(); findPropCache = new SoftReference<ConcurrentMap<String, IDefinition>>(map); } } } return map; } private ConcurrentMap<QName, IDefinition> getQualifiedScopeChainMap() { ConcurrentMap<QName, IDefinition> map = findPropQualifiedCache != null ? findPropQualifiedCache.get() : null; if (map == null) { synchronized (this) { // Check again, in case another thread updated the map first map = findPropQualifiedCache != null ? findPropQualifiedCache.get() : null; if (map == null) { map = mapMaker.<QName, IDefinition> makeMap(); findPropQualifiedCache = new SoftReference<ConcurrentMap<QName, IDefinition>>(map); } } } return map; } /** * Version of findPropertyQualified that uses a cache. Checks the cache * first, and only queries the scope if the we don't have a cached result. * * @param scope The scope to perform the lookup in * @param name Name of the property to find * @param cache ASDefinitionCache to use for the lookup - this is only used * to get at the ICompilerProject * @param dt Which type of dependency to introduce when we do the lookup * @return The IDefinition for the property, or null if it wasn't found */ IDefinition findPropertyQualified(INamespaceDefinition qualifier, String name, DependencyType dt) { QName qname = new QName(name, qualifier); ConcurrentMap<QName, IDefinition> map = getQualifiedScopeChainMap(); IDefinition result = map.get(qname); if (result != null) { assert result.isInProject(project); // We found a cached result - we're done return result; } // If we get this far, then we did not find a cached entry // It is possible for 2+ threads to get in here for the same name. // This is intentional - the worst that happens is that we duplicate the resolution work // the benefit is that we avoid any sort of locking, which was proving expensive (time wise, // and memory wise). IDefinition def; // Look for the definition in the scope Set<INamespaceDefinition> namespaceSet = Collections.singleton(qualifier); List<IDefinition> defs = scope.findProperty(project, name, namespaceSet, dt); switch (defs.size()) { case 0: def = null; break; case 1: def = defs.get(0); assert def.isInProject(project); break; default: IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs); if (d != null) def = d; else def = AmbiguousDefinition.get(); break; } if (def != null) { assert def.isInProject(project); assert result == null; // If we have a non-null dependency type // then we can cache the result of the name resolution. // If the dependency type is null we can't cache the name // resolution result, because the name resolution cache will not // be properly invalidated when the file containing the definition changes. if (dt != null) { result = map.putIfAbsent(qname, def); if (result == null) result = def; } else { result = def; } } return result; } /** * Resolves the specified reference to a definition and adds a dependency to * the dependency graph if needed. * <p> * This method is only public so that the implementation of * IResolveQualifiersReference.resolve can call it. This method should only * be called from the implementation of {@link IResolvedQualifiersReference}. * * @param ref The reference to resolve. * @param dt The type of dependency to add if a new edge needs to be added * to the dependency graph. * @return The definition the reference resolves to, null, or the ambiguous * definition. */ public IDefinition findPropertyMultiname(IResolvedQualifiersReference ref, DependencyType dt) { ConcurrentMap<IResolvedQualifiersReference, IDefinition> cache = getMultinameLookupMap(); IDefinition result = cache.get(ref); if (result != null) return result; IDefinition def; // Look for the definition in the scope List<IDefinition> defs = scope.findProperty(project, ref.getName(), ref.getQualifiers(), dt); switch (defs.size()) { case 0: // No definition found! def = null; break; case 1: // found single definition! def = defs.get(0); assert def.isInProject(project); break; default: IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs); if (d != null) def = d; else def = AmbiguousDefinition.get(); } if (def != null) { assert def.isInProject(project); assert result == null; // If we have a non-null dependency type // then we can cache the result of the name resolution. // If the dependency type is null we can't cache the name // resolution result, because the name resolution cache will not // be properly invalidated when the file containing the definition changes. if (dt != null) { result = cache.putIfAbsent(ref, def); if (result == null) result = def; } else { result = def; } } return result; } /** * Version of getNamespaceSet that caches the results. * * @return the namespace set to use for unqualified lookups in this scope */ Set<INamespaceDefinition> getNamespaceSet() { Set<INamespaceDefinition> nsSet = openNamespaceCache != null ? openNamespaceCache.get() : null; if (nsSet != null) return nsSet; nsSet = scope.getNamespaceSetImpl(project); openNamespaceCache = new SoftReference<Set<INamespaceDefinition>>(nsSet); return nsSet; } private ConcurrentMap<String, Set<INamespaceDefinition>> getNamespacesForNameMap() { ConcurrentMap<String, Set<INamespaceDefinition>> map = namespacesForNameCache != null ? namespacesForNameCache.get() : null; if (map == null) { synchronized (this) { // Check again, in case another thread updated the map first map = namespacesForNameCache != null ? namespacesForNameCache.get() : null; if (map == null) { map = mapMaker.<String, Set<INamespaceDefinition>> makeMap(); namespacesForNameCache = new SoftReference<ConcurrentMap<String, Set<INamespaceDefinition>>>(map); } } } return map; } private ConcurrentMap<IResolvedQualifiersReference, IDefinition> getMultinameLookupMap() { ConcurrentMap<IResolvedQualifiersReference, IDefinition> map = multinameLookupCache != null ? multinameLookupCache.get() : null; if (map == null) { synchronized (this) { // Check again, in case another thread updated the map first map = multinameLookupCache != null ? multinameLookupCache.get() : null; if (map == null) { map = mapMaker.<IResolvedQualifiersReference, IDefinition> makeMap(); multinameLookupCache = new SoftReference<ConcurrentMap<IResolvedQualifiersReference, IDefinition>>(map); } } } return map; } private ConcurrentMap<IDefinition, Object> getConstantValueLookupMap() { ConcurrentMap<IDefinition, Object> map = constValueLookupCache != null ? constValueLookupCache.get() : null; if (map == null) { synchronized (this) { // Check again, in case another thread updated the map first map = constValueLookupCache != null ? constValueLookupCache.get() : null; if (map == null) { map = mapMaker.<IDefinition, Object> makeMap(); constValueLookupCache = new SoftReference<ConcurrentMap<IDefinition, Object>>(map); } } } return map; } private Set<IASLanguageConstants.BuiltinType> getBuiltinTypeMap() { Set<IASLanguageConstants.BuiltinType> set = builtinTypeDependencyCache != null ? builtinTypeDependencyCache.get() : null; if (set == null) { synchronized (this) { // Check again, in case another thread updated the set first set = builtinTypeDependencyCache != null ? builtinTypeDependencyCache.get() : null; if (set == null) { set = Sets.newSetFromMap(mapMaker.<IASLanguageConstants.BuiltinType, Boolean> makeMap()); builtinTypeDependencyCache = new SoftReference<Set<IASLanguageConstants.BuiltinType>>(set); } } } return set; } /** * Determines if the {@link TypeScope} this cache is associated with needs * an implicit 'implements flash.events.IEventDispatcher' due to the class, * or some of its members being marked bindable. The result of this method * is cached. * <p> * Only * {@link ClassDefinitionBase#needsEventDispatcher(ICompilerProject)} * should call this method. All other code should call * {@link ClassDefinitionBase#needsEventDispatcher(ICompilerProject)}. * * @return true if this class needs to add IEventDispatcher to its interface * list, and should implement the IEventDispatcher methods. */ public boolean needsEventDispatcher() { assert scope instanceof TypeScope : "needsEventDispatcher should only be called on scope cache's for TypeScopes!"; assert scope.getDefinition() instanceof ClassDefinitionBase : "needsEventDispatcher should only be called on scope cache's for the scopes contained by classes!"; Boolean valueObject = needsEventDispatcherCache; if (valueObject != null) return valueObject.booleanValue(); synchronized (this) { // Check again, in case another thread updated the value first valueObject = needsEventDispatcherCache; if (valueObject != null) return valueObject.booleanValue(); boolean computedValue = ((ClassDefinitionBase)scope.getDefinition()).computeNeedsEventDispatcher(project); needsEventDispatcherCache = computedValue; return computedValue; } } public IInterfaceDefinition[] resolveInterfaces() { assert scope instanceof TypeScope : "resolveInterfacesImpl should only be called on scope cache's for TypeScopes!"; assert scope.getDefinition() instanceof TypeDefinitionBase : "resolveInterfacesImpl should only be called on scope cache's for the scopes contained by types!"; IInterfaceDefinition[] interfs = interfacesCache != null ? interfacesCache.get() : null; if( interfs != null ) return interfs; synchronized (this) { // check again in case another thread updated the value first interfs = interfacesCache != null ? interfacesCache.get() : null; if( interfs != null ) return interfs; interfs = ((TypeDefinitionBase)scope.getDefinition()).resolveInterfacesImpl(project); interfacesCache = new SoftReference<IInterfaceDefinition[]>(interfs); return interfs; } } /** * Version of getNamespaceSetForName that caches the results - this is used * to get the namespace set to use to lookup a name - If name is an * explicitly imported definition, then the namespace set will consist of * the package name from the import(s) plus the open namespace set. If name * was not explitly imported then the open namespace set will be returned * * @param name the name to lookup * @return the namespace set to use to lookup name */ Set<INamespaceDefinition> getNamespaceSetForName(String name) { ConcurrentMap<String, Set<INamespaceDefinition>> map = getNamespacesForNameMap(); Set<INamespaceDefinition> result = map.get(name); if (result != null) { // We found a cached result - we're done return result; } // It is possible for 2+ threads to get in here for the same name. // This is intentional - the worst that happens is that we duplicate the resolution work // the benefit is that we avoid any sort of locking, which was proving expensive (time wise, // and memory wise). Set<INamespaceDefinition> newResult = scope.getNamespaceSetForNameImpl(project, name); result = map.putIfAbsent(name, newResult); if (result == null) result = newResult; return result; } /** * Version of addDependencyOnBuiltinType that uses a cache to determine if the dependency actually * needs to be added. The act of adding a dependency is somewhat expensive, so using a cache * is much faster * * @param builtinType the type to depend on * @param dt the type of Dependency to add */ void addDependencyOnBuiltinType(IASLanguageConstants.BuiltinType builtinType, DependencyType dt) { Set<IASLanguageConstants.BuiltinType> set = getBuiltinTypeMap(); if( set.contains(builtinType) ) { // We found a cached result - we're done return; } // It is possible for 2+ threads to get in here for the same name. // This is intentional - the worst that happens is that we duplicate the dependency work // the benefit is that we avoid any sort of locking, which was proving expensive (time wise, // and memory wise). scope.addDependencyOnBuiltinTypeImpl(project, builtinType, dt); set.add(builtinType); return; } /** * Used to cache no constant value results for the const value cache. * Computing the value is expensive whether there is a value or not, and * many constants will not have compile time constant values, so we want to * cache those as well. */ private static final Object NO_CONST_VALUE = new Object(); /** * get the constant value for the given const definition. If a compile time * constant can not be computed for the definition, this will return null. * * @param constDef The constant definition you want the constant value of * @return The constant value, or null if a compie time constant could not * be computed */ public Object getConstantValue(ConstantDefinition constDef) { ConcurrentMap<IDefinition, Object> map = getConstantValueLookupMap(); Object result = map.get(constDef); if (result != null) { // We found a cached result - we're done if (result == NO_CONST_VALUE) return null; return result; } // It is possible for 2+ threads to get in here for the same name. // This is intentional - the worst that happens is that we duplicate the resolution work // the benefit is that we avoid any sort of locking, which was proving expensive (time wise, // and memory wise). Object newResult = constDef.resolveValueImpl(project); if (newResult == null) newResult = NO_CONST_VALUE; result = map.putIfAbsent(constDef, newResult); if (result == null) result = newResult; if (result == NO_CONST_VALUE) return null; else return result; } /** * Helper class - to be used as a key for caching qualified name lookups. */ private static class QName { String name; INamespaceDefinition ns; QName(String name, INamespaceDefinition ns) { assert ns != null; this.name = name; this.ns = ns; } @Override public int hashCode() { return name.hashCode() ^ ns.hashCode(); } @Override public boolean equals(Object o) { if (o == this) return true; if (o instanceof QName) { QName other = (QName)o; return name.equals(other.name) && ns.equals(other.ns); } return false; } } }