/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.core.model; import java.util.*; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.dltk.core.*; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.dltk.core.search.IDLTKSearchScope; import org.eclipse.dltk.core.search.SearchEngine; import org.eclipse.dltk.internal.core.hierarchy.TypeHierarchy; import org.eclipse.php.core.PHPToolkitUtil; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.internal.core.filenetwork.FileNetworkUtility; import org.eclipse.php.internal.core.filenetwork.ReferenceTree; import org.eclipse.php.internal.core.typeinference.IModelAccessCache; import org.eclipse.php.internal.core.typeinference.PHPModelUtils; /** * This class can be used for caching model access results during a sequence of * processes that run on the same file at a time when model updates are * impossible. Each method first searches for all elements using a '*' pattern, * then it caches results for a subsequent queries. This class is not thread * safe. * * @author Michael */ public class PerFileModelAccessCache implements IModelAccessCache { private ISourceModule sourceModule; private Map<IType, ITypeHierarchy> hierarchyCache = Collections .synchronizedMap(new HashMap<IType, ITypeHierarchy>()); private Map<String, Collection<IMethod>> globalFunctionsCache; private Map<String, Collection<IType>> allTypesCache; private Map<String, Collection<IType>> allTraitsCache; private Map<String, Collection<IType>> allNamespacesCache; private ReferenceTree fileHierarchy; private static class FakeTypeHierarchy extends TypeHierarchy { public FakeTypeHierarchy() { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=494388 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=498339 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=504013 // We must initialize the internal properties to avoid NPEs // when using this class methods. initialize(1); } } /** * Constructs new cache * * @param sourceModule * Current file */ public PerFileModelAccessCache(ISourceModule sourceModule) { this.sourceModule = sourceModule; allTraitsCache = Collections.synchronizedMap(new HashMap<String, Collection<IType>>()); allTypesCache = Collections.synchronizedMap(new HashMap<String, Collection<IType>>()); allNamespacesCache = Collections.synchronizedMap(new HashMap<String, Collection<IType>>()); } public ISourceModule getSourceModule() { return sourceModule; } public ITypeHierarchy getSuperTypeHierarchy(IType type, IProgressMonitor monitor) throws ModelException { if (!PHPToolkitUtil.isFromPHPProject(type)) { return new FakeTypeHierarchy(); } ITypeHierarchy hierarchy = hierarchyCache.get(type); if (hierarchy == null) { hierarchy = type.newSupertypeHierarchy(monitor); hierarchyCache.put(type, hierarchy); } return hierarchy; } /** * Analyzes file dependences, and builds tree of all source modules, which * are referenced by the given source module. * * @param sourceModule * Current source module * @param monitor * Progress monitor */ public ReferenceTree getFileHierarchy(ISourceModule sourceModule, IProgressMonitor monitor) { if (!this.sourceModule.equals(sourceModule)) { // Invoke a new search, since we only cache for the original file in // this class: return FileNetworkUtility.buildReferencedFilesTree(sourceModule, monitor); } if (fileHierarchy == null) { fileHierarchy = FileNetworkUtility.buildReferencedFilesTree(sourceModule, monitor); } return fileHierarchy; } /** * Filters given set of elements according to a file network * * @param sourceModule * Current file * @param elements * Elements set * @param monitor * Progress monitor * @return filtered elements */ public <T extends IModelElement> Collection<T> filterModelElements(ISourceModule sourceModule, Collection<T> elements, IProgressMonitor monitor) { return PHPModelUtils.fileNetworkFilter(sourceModule, elements, this, monitor); } /** * Filters given set of elements according to a file network, but only if * all elements represent the same type, name and namespace * * @param sourceModule * Current file * @param elements * Elements set * @param monitor * Progress monitor * @return filtered elements */ public <T extends IModelElement> Collection<T> filterSameModelElements(ISourceModule sourceModule, Collection<T> elements, IProgressMonitor monitor) { return PHPModelUtils.filterElements(sourceModule, elements, this, monitor); } /** * Returns cached result of a function search, or invokes a new search query * * @param sourceModule * Current source module * @param functionName * The name of the global function * @param monitor * Progress monitor * @return a collection of functions according to a given name, or * <code>null</code> if not found */ public Collection<IMethod> getGlobalFunctions(ISourceModule sourceModule, String functionName, IProgressMonitor monitor) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=342465 if (functionName == null) { return new ArrayList<IMethod>(); } Collection<IMethod> functions; if (!this.sourceModule.equals(sourceModule)) { // Invoke a new search, since we only cache for the original file in // this class: IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); functions = Arrays.asList( PHPModelAccess.getDefault().findFunctions(functionName, MatchRule.EXACT, 0, 0, scope, monitor)); } else { functionName = functionName.toLowerCase(); if (globalFunctionsCache == null) { globalFunctionsCache = Collections.synchronizedMap(new HashMap<String, Collection<IMethod>>()); if (!globalFunctionsCache.containsKey(functionName)) { IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); IMethod[] allFunctions = PHPModelAccess.getDefault().findFunctions(functionName, MatchRule.EXACT, 0, 0, scope, monitor); Collection<IMethod> funcList = new ArrayList<IMethod>(allFunctions.length); for (IMethod function : allFunctions) { funcList.add(function); } globalFunctionsCache.put(functionName, funcList); } } functions = globalFunctionsCache.get(functionName); } return filterModelElements(sourceModule, functions, monitor); } /** * Returns cached result of a type search, or invokes a new search query * * @param sourceModule * Current source module * @param typeName * The name of the type (class, interface or namespace) * @param namespaceName * namespace name * @param monitor * Progress monitor * @return a collection of types according to a given name, or * <code>null</code> if not found */ public Collection<IType> getTypes(ISourceModule sourceModule, String typeName, String namespaceName, IProgressMonitor monitor) { Collection<IType> types; if (!this.sourceModule.equals(sourceModule)) { // Invoke a new search, since we only cache for the original file in // this class: IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); types = Arrays.asList( PHPModelAccess.getDefault().findTypes(namespaceName, typeName, MatchRule.EXACT, 0, 0, scope, null)); } else { typeName = typeName.toLowerCase(); // if the namespace is not blank, append it to the key. final StringBuilder key = new StringBuilder(); if (namespaceName != null && StringUtils.isNotBlank(namespaceName)) { String nameSpace = namespaceName; if (namespaceName.startsWith("\\") //$NON-NLS-1$ || namespaceName.startsWith("/")) { //$NON-NLS-1$ nameSpace = namespaceName.substring(1); } if (nameSpace.length() > 0) { key.append(nameSpace.toLowerCase()).append("$"); //$NON-NLS-1$ } } key.append(typeName); final String searchFor = key.toString(); if (!allTypesCache.containsKey(searchFor)) { IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); allTypesCache.put(searchFor, Arrays.asList(PHPModelAccess.getDefault().findTypes(namespaceName, typeName, MatchRule.EXACT, 0, 0, scope, null))); } types = allTypesCache.get(searchFor); } return filterSameModelElements(sourceModule, types, monitor); } /** * Returns cached result of a namespace search, or invokes a new search * query * * @param sourceModule * Current source module * @param namespaceName * namespace name * @param monitor * Progress monitor * @return namespaces collection if found, otherwise <code>null</code> */ public Collection<IType> getNamespaces(ISourceModule sourceModule, String namespaceName, IProgressMonitor monitor) { Collection<IType> namespaces; if (!this.sourceModule.equals(sourceModule)) { // Invoke a new search, since we only cache for the original file in // this class: IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); namespaces = Arrays.asList(PHPModelAccess.getDefault().findNamespaces(null, namespaceName, MatchRule.EXACT, 0, 0, scope, null)); } else { final String searchFor = namespaceName.toLowerCase(); if (!allNamespacesCache.containsKey(searchFor)) { IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); allNamespacesCache.put(searchFor, Arrays.asList(PHPModelAccess.getDefault().findNamespaces(null, namespaceName, MatchRule.EXACT, 0, 0, scope, null))); } namespaces = allNamespacesCache.get(searchFor); } return namespaces; } /** * Returns cached result of a trait search, or invokes a new search query * * @param sourceModule * Current source module * @param typeName * The name of the trait * @param namespaceName * namespace name * @param monitor * Progress monitor * @return a collection of traits according to a given name, or * <code>null</code> if not found */ public Collection<IType> getTraits(ISourceModule sourceModule, String typeName, String namespaceName, IProgressMonitor monitor) { Collection<IType> types; if (!this.sourceModule.equals(sourceModule)) { // Invoke a new search, since we only cache for the original file in // this class: IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); types = Arrays.asList(PHPModelAccess.getDefault().findTraits(typeName, MatchRule.EXACT, 0, 0, scope, null)); } else { typeName = typeName.toLowerCase(); // if the namespace is not blank, append it to the key. final StringBuilder key = new StringBuilder(); if (namespaceName != null && StringUtils.isNotBlank(namespaceName)) { String nameSpace = namespaceName; if (namespaceName.startsWith("\\") //$NON-NLS-1$ || namespaceName.startsWith("/")) { //$NON-NLS-1$ nameSpace = namespaceName.substring(1); } if (nameSpace.length() > 0) { key.append(nameSpace.toLowerCase()).append("$"); //$NON-NLS-1$ } } key.append(typeName); final String searchFor = key.toString(); if (!allTraitsCache.containsKey(searchFor)) { IScriptProject scriptProject = sourceModule.getScriptProject(); IDLTKSearchScope scope = SearchEngine.createSearchScope(scriptProject); allTraitsCache.put(searchFor, Arrays.asList(PHPModelAccess.getDefault().findTraits(namespaceName, typeName, MatchRule.EXACT, 0, 0, scope, null))); } types = allTraitsCache.get(searchFor); } return filterSameModelElements(sourceModule, types, monitor); } /** * Searches for classes by name * * @param sourceModule * Current source module * @param name * Class name * @param namespaceName * namespace name * @param monitor * Progress monitor * @return classes collection if found, otherwise <code>null</code> * @throws ModelException */ public Collection<IType> getClasses(ISourceModule sourceModule, String name, String namespaceName, IProgressMonitor monitor) throws ModelException { Collection<IType> allTypes = getTypes(sourceModule, name, namespaceName, monitor); if (allTypes == null) { return null; } Collection<IType> result = new LinkedList<IType>(); for (IType type : allTypes) { if (PHPFlags.isClass(type.getFlags())) { result.add(type); } } return result; } /** * Searches for interfaces by name * * @param sourceModule * Current source module * @param name * Interface name * @param namespaceName * namespace name * @param monitor * Progress monitor * @return interfaces collection if found, otherwise <code>null</code> * @throws ModelException */ public Collection<IType> getInterfaces(ISourceModule sourceModule, String name, String namespaceName, IProgressMonitor monitor) throws ModelException { Collection<IType> allTypes = getTypes(sourceModule, name, namespaceName, monitor); if (allTypes == null) { return null; } Collection<IType> result = new LinkedList<IType>(); for (IType type : allTypes) { if (PHPFlags.isInterface(type.getFlags())) { result.add(type); } } return result; } /** * Searches for classes or interfaces by name * * @param sourceModule * Current source module * @param name * Class name * @param namespaceName * namespace name * @param monitor * Progress monitor * @return classes collection if found, otherwise <code>null</code> * @throws ModelException */ public Collection<IType> getClassesOrInterfaces(ISourceModule sourceModule, String name, String namespaceName, IProgressMonitor monitor) throws ModelException { return getTypes(sourceModule, name, namespaceName, monitor); } }