blob: 5961c4676c2d1eed160ced15dae9b0bd05a0d763 [file] [log] [blame]
/*
*
* 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.royale.compiler.internal.scopes;
import org.apache.royale.compiler.common.DependencyType;
import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
import org.apache.royale.compiler.constants.IASLanguageConstants;
import org.apache.royale.compiler.definitions.IDefinition;
import org.apache.royale.compiler.definitions.IFunctionDefinition;
import org.apache.royale.compiler.definitions.IInterfaceDefinition;
import org.apache.royale.compiler.definitions.INamespaceDefinition;
import org.apache.royale.compiler.definitions.ITypeDefinition;
import org.apache.royale.compiler.definitions.references.IResolvedQualifiersReference;
import org.apache.royale.compiler.internal.definitions.AmbiguousDefinition;
import org.apache.royale.compiler.internal.definitions.ClassDefinitionBase;
import org.apache.royale.compiler.internal.definitions.ConstantDefinition;
import org.apache.royale.compiler.internal.definitions.TypeDefinitionBase;
import org.apache.royale.compiler.internal.projects.CompilerProject;
import org.apache.royale.compiler.projects.ICompilerProject;
import org.apache.royale.compiler.units.ICompilationUnit;
import com.google.common.collect.MapMaker;
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, boolean favorTypes)
{
ConcurrentMap<String, IDefinition> map = getScopeChainMap();
IDefinition result = map.get(name);
if (result != null)
{
// We found a cached result - we're done
// after making sure it has a dependency
if (result instanceof ITypeDefinition)
{
ICompilationUnit from = scope.getFileScope().getCompilationUnit();
assert result.isInProject(project);
String qname = result.getQualifiedName();
ICompilationUnit to = ((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(result);
if (to == null && !(qname.contentEquals("void") || qname.contentEquals("*")))
System.out.println("No compilation unit for " + qname);
if (to != null)
project.addDependency(from, to, dt, qname);
}
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).
boolean wasAmbiguous = false;
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:
wasAmbiguous = true;
IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs, favorTypes);
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 && !wasAmbiguous)
{
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)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in getScopeChainMap");
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);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in getScopeChainMap");
}
return map;
}
private ConcurrentMap<QName, IDefinition> getQualifiedScopeChainMap()
{
ConcurrentMap<QName, IDefinition> map = findPropQualifiedCache != null ? findPropQualifiedCache.get() : null;
if (map == null)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in getQualifiedScopeChainMap");
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);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in getQualifiedScopeChainMap");
}
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, false);
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, false);
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)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in getNamespacesForNameMap");
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);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in getNamespacesForNameMap");
}
return map;
}
private ConcurrentMap<IResolvedQualifiersReference, IDefinition> getMultinameLookupMap()
{
ConcurrentMap<IResolvedQualifiersReference, IDefinition> map = multinameLookupCache != null ? multinameLookupCache.get() : null;
if (map == null)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in getMultinameLookupMap");
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);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in getMultinameLookupMap");
}
return map;
}
private ConcurrentMap<IDefinition, Object> getConstantValueLookupMap()
{
ConcurrentMap<IDefinition, Object> map = constValueLookupCache != null ? constValueLookupCache.get() : null;
if (map == null)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in getConstantValueLookupMap");
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);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in getConstantValueLookupMap");
}
return map;
}
private Set<IASLanguageConstants.BuiltinType> getBuiltinTypeMap()
{
Set<IASLanguageConstants.BuiltinType> set = builtinTypeDependencyCache != null ? builtinTypeDependencyCache.get() : null;
if (set == null)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in getBuiltinTypeMap");
synchronized (this)
{
// Check again, in case another thread updated the set first
set = builtinTypeDependencyCache != null ? builtinTypeDependencyCache.get() : null;
if (set == null)
{
set = Collections.newSetFromMap(mapMaker.<IASLanguageConstants.BuiltinType, Boolean> makeMap());
builtinTypeDependencyCache = new SoftReference<Set<IASLanguageConstants.BuiltinType>>(set);
}
}
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in getBuiltinTypeMap");
}
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();
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in needsEventDispatcher");
synchronized (this)
{
// Check again, in case another thread updated the value first
valueObject = needsEventDispatcherCache;
if (valueObject != null)
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in needsEventDispatcher");
return valueObject.booleanValue();
}
boolean computedValue = ((ClassDefinitionBase)scope.getDefinition()).computeNeedsEventDispatcher(project);
needsEventDispatcherCache = computedValue;
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in needsEventDispatcher");
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;
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache waiting for lock in resolveInterfaces");
synchronized (this)
{
// check again in case another thread updated the value first
interfs = interfacesCache != null ? interfacesCache.get() : null;
if( interfs != null )
{
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in resolveInterfaces");
return interfs;
}
interfs = ((TypeDefinitionBase)scope.getDefinition()).resolveInterfacesImpl(project);
interfacesCache = new SoftReference<IInterfaceDefinition[]>(interfs);
if ((CompilerDiagnosticsConstants.diagnostics & CompilerDiagnosticsConstants.ASSCOPECACHE) == CompilerDiagnosticsConstants.ASSCOPECACHE)
System.out.println("ASScopeCache done with lock in resolveInterfaces");
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;
}
}
}