/*******************************************************************************
* Copyright (c) 2009, 2016 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.typeinference;
import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.dltk.annotations.NonNull;
import org.eclipse.dltk.annotations.Nullable;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.Modifiers;
import org.eclipse.dltk.ast.declarations.MethodDeclaration;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.declarations.TypeDeclaration;
import org.eclipse.dltk.ast.expressions.Expression;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.ast.statements.Statement;
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.ModelElement;
import org.eclipse.dltk.internal.core.SourceField;
import org.eclipse.dltk.internal.core.SourceRefElement;
import org.eclipse.jface.text.IRegion;
import org.eclipse.php.core.PHPToolkitUtil;
import org.eclipse.php.core.PHPVersion;
import org.eclipse.php.core.ast.nodes.Identifier;
import org.eclipse.php.core.ast.nodes.NamespaceName;
import org.eclipse.php.core.compiler.PHPFlags;
import org.eclipse.php.core.compiler.ast.nodes.*;
import org.eclipse.php.core.compiler.ast.visitor.PHPASTVisitor;
import org.eclipse.php.core.project.ProjectOptions;
import org.eclipse.php.internal.core.Constants;
import org.eclipse.php.internal.core.Logger;
import org.eclipse.php.internal.core.PHPCoreConstants;
import org.eclipse.php.internal.core.PHPCorePlugin;
import org.eclipse.php.internal.core.compiler.ast.parser.ASTUtils;
import org.eclipse.php.internal.core.filenetwork.FileNetworkUtility;
import org.eclipse.php.internal.core.filenetwork.ReferenceTree;
import org.eclipse.php.internal.core.language.LanguageModelInitializer;
import org.eclipse.php.internal.core.model.PHPModelAccess;
import org.eclipse.php.internal.core.typeinference.DeclarationSearcher.DeclarationType;
import org.eclipse.php.internal.core.util.text.PHPTextSequenceUtilities;
import org.eclipse.php.internal.core.util.text.TextSequence;
public class PHPModelUtils {
/**
* User-readable string for separating list items (e.g. ", ").
*/
public final static String COMMA_STRING = ", "; //$NON-NLS-1$
public static final String ENCLOSING_TYPE_SEPARATOR = new String(
new char[] { NamespaceReference.NAMESPACE_SEPARATOR }); // $NON-NLS-1$
private static final IType[] EMPTY_TYPES = new IType[0];
/**
* Concatenates all type names (without their namespace prefixes) together
*
* @param references
* list of type references
* @return concatenated type names
*/
@NonNull
public static String appendTypeReferenceNames(List<TypeReference> references) {
if (references.isEmpty()) {
return ""; //$NON-NLS-1$
}
if (references.size() == 1) {
return references.get(0).getName();
}
StringBuilder sb = new StringBuilder();
sb.append(references.get(0).getName());
for (int i = 1; i < references.size(); i++) {
TypeReference reference = references.get(i);
sb.append(Constants.TYPE_SEPARATOR_CHAR);
sb.append(reference.getName());
}
return sb.toString();
}
/**
* Extracts the element name from the given fully qualified name
*
* @param element
* Element name
* @return element name without the namespace prefix
*/
@Nullable
public static String extractElementName(@Nullable String element) {
if (element != null) {
int i = element.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (i != -1) {
element = element.substring(i + 1).trim();
}
}
return element;
}
/**
* Extracts the name space name from the given fully qualified name
*
* @param element
* Element name
* @return namespace prefix
*/
@Nullable
public static String extractNameSpaceName(@Nullable String element) {
String nameSpaceName = null;
if (element != null) {
int i = element.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (i != -1) {
nameSpaceName = element.substring(0, i).trim();
}
}
return nameSpaceName;
}
/**
* Concatenate FQN parameters into one string e.g. 'A\B' + 'C\D' = 'A\B\C\D'
*
* @param fqns
* names to concat
* @return concatenated names
*/
@NonNull
public static String concatFullyQualifiedNames(String... fqns) {
StringBuilder builder = new StringBuilder();
for (String fqn : fqns) {
if (fqn != null) {
if (builder.length() != 0
&& builder.charAt(builder.length() - 1) != NamespaceReference.NAMESPACE_SEPARATOR
&& !fqn.isEmpty() && fqn.charAt(0) != NamespaceReference.NAMESPACE_SEPARATOR) {
builder.append(NamespaceReference.NAMESPACE_SEPARATOR);
}
builder.append(fqn);
}
}
return builder.toString();
}
/**
* if the elementName is a class alias for a namespace class, we get its
* original name from its alias
*
* @param elementName
* @param sourceModule
* @param offset
* @param defaultClassName
* @return
*/
public static String getRealName(String elementName, ISourceModule sourceModule, final int offset,
String defaultClassName) {
// Check class name aliasing:
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
UsePart usePart = ASTUtils.findUseStatementByAlias(moduleDeclaration, elementName, offset);
if (usePart != null) {
elementName = usePart.getNamespace().getFullyQualifiedName();
int nsIndex = elementName.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (nsIndex != -1) {
defaultClassName = elementName.substring(nsIndex + 1);
} else {
defaultClassName = elementName;
}
}
return defaultClassName;
}
/**
* Extracts the namespace name from the specified element name and resolves
* it using USE statements that present in the file.
*
* @param elementName
* The name of the element, like: \A\B or A\B\C.
* @param sourceModule
* Source module where the element is referenced
* @param offset
* The offset where element is referenced
* @return namespace name:
*
* <pre>
* 1. <code>""</code> (empty string) indicates global namespace
* 2. non-empty string indicates a real namespace
* 3. <code>null</code> indicates that there's no namespace prefix in element name
* </pre>
*/
@Nullable
public static String extractNamespaceName(String elementName, ISourceModule sourceModule, final int offset) {
// Check class name aliasing:
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
UsePart usePart = ASTUtils.findUseStatementByAlias(moduleDeclaration, elementName, offset);
if (usePart != null) {
elementName = usePart.getNamespace().getFullyQualifiedName();
if (elementName != null && elementName.length() > 0
&& elementName.charAt(0) != NamespaceReference.NAMESPACE_SEPARATOR) {
elementName = NamespaceReference.NAMESPACE_SEPARATOR + elementName;
}
}
boolean isGlobal = false;
int nsIndex = -1;
if (elementName != null) {
nsIndex = elementName.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (elementName.length() > 0 && elementName.charAt(0) == NamespaceReference.NAMESPACE_SEPARATOR) {
isGlobal = true;
}
}
if (nsIndex != -1) {
String namespace = elementName == null ? "" : elementName.substring(0, nsIndex); //$NON-NLS-1$
if (isGlobal && namespace.length() > 0) {
namespace = namespace.substring(1);
}
if (!isGlobal) {
// 1. It can be a special 'namespace' keyword, which points to
// the current namespace:
if ("namespace".equalsIgnoreCase(namespace)) { //$NON-NLS-1$
IType currentNamespace = PHPModelUtils.getCurrentNamespace(sourceModule, offset);
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=470673
return currentNamespace != null ? currentNamespace.getElementName() : null;
}
// 2. it can be an alias - try to find relevant USE statement
if (namespace.indexOf('\\') == -1) {
usePart = ASTUtils.findUseStatementByAlias(moduleDeclaration, namespace, offset);
if (usePart != null) {
return usePart.getNamespace().getFullyQualifiedName();
}
} else {
nsIndex = namespace.indexOf(NamespaceReference.NAMESPACE_SEPARATOR);
String alias = namespace.substring(0, nsIndex);
usePart = ASTUtils.findUseStatementByAlias(moduleDeclaration, alias, offset);
if (usePart != null) {
return usePart.getNamespace().getFullyQualifiedName() + NamespaceReference.NAMESPACE_SEPARATOR
+ namespace.substring(nsIndex + 1);
}
}
// 3. it can be a sub-namespace of the current namespace:
IType currentNamespace = PHPModelUtils.getCurrentNamespace(sourceModule, offset);
if (currentNamespace != null) {
return new StringBuilder(currentNamespace.getElementName())
.append(NamespaceReference.NAMESPACE_SEPARATOR).append(namespace).toString();
}
}
// global namespace:
return namespace;
}
return null;
}
/**
* Filters model elements using file network.
*
* @param sourceModule
* Source module
* @param elements
* Model elements to filter
* @param cache
* Temporary model cache instance
* @param monitor
* Progress monitor
* @return
*/
@Nullable
public static <T extends IModelElement> Collection<T> fileNetworkFilter(ISourceModule sourceModule,
@Nullable Collection<T> elements, IModelAccessCache cache, IProgressMonitor monitor) {
// If it's just one element (or less) - return it
if (elements != null && elements.size() > 1) {
List<T> filteredElements = new LinkedList<T>();
// If some of elements belong to current file just return it:
for (T element : elements) {
if (sourceModule.equals(element.getOpenable())) {
filteredElements.add(element);
}
}
if (filteredElements.size() == 0) {
ReferenceTree referenceTree;
if (cache != null) {
referenceTree = cache.getFileHierarchy(sourceModule, monitor);
} else {
// Filter by includes network
referenceTree = FileNetworkUtility.buildReferencedFilesTree(sourceModule, monitor);
}
for (T element : elements) {
if (LanguageModelInitializer.isLanguageModelElement(element)
|| referenceTree.find(((ModelElement) element).getSourceModule())) {
filteredElements.add(element);
}
}
}
if (filteredElements.size() > 0) {
elements = filteredElements;
}
}
return elements;
}
@Nullable
public static <T extends IModelElement> Collection<T> fileNetworkFilterTypes(ISourceModule sourceModule,
@Nullable Collection<T> elements, IModelAccessCache cache, boolean isNs, IProgressMonitor monitor) {
if (elements != null && elements.size() > 0) {
List<T> filteredElements = new LinkedList<T>();
// If some of elements belong to current file return just it:
for (T element : elements) {
try {
if (sourceModule.equals(element.getOpenable())
&& (isNs && PHPFlags.isNamespace(((IType) element).getFlags())
|| !isNs && !PHPFlags.isNamespace(((IType) element).getFlags()))) {
filteredElements.add(element);
}
} catch (ModelException e) {
}
}
if (filteredElements.size() == 0) {
ReferenceTree referenceTree;
if (cache != null) {
referenceTree = cache.getFileHierarchy(sourceModule, monitor);
} else {
// Filter by includes network
referenceTree = FileNetworkUtility.buildReferencedFilesTree(sourceModule, monitor);
}
for (T element : elements) {
if (LanguageModelInitializer.isLanguageModelElement(element)
|| referenceTree.find(((ModelElement) element).getSourceModule())) {
try {
if ((isNs && PHPFlags.isNamespace(((IType) element).getFlags())
|| !isNs && !PHPFlags.isNamespace(((IType) element).getFlags()))) {
filteredElements.add(element);
}
} catch (ModelException e) {
}
}
}
}
if (filteredElements.size() > 0) {
elements = filteredElements;
}
}
return elements;
}
/**
* Determine whether given elements represent the same type, name and
* namespace, but declared in different files (determine whether file
* network filtering can be used)
*
* @param elements
* Model elements list
* @return
*/
private static <T extends IModelElement> boolean canUseFileNetworkFilter(Collection<T> elements) {
int elementType = 0;
String elementName = null;
String namespaceName = null;
for (T element : elements) {
if (element == null) {
continue;
}
IType namespaceElement = getCurrentNamespace(element);
String namespaceNameElement = namespaceElement != null ? namespaceElement.getElementName() : null;
if (elementName == null) {
elementType = element.getElementType();
elementName = element.getElementName();
namespaceName = namespaceNameElement;
continue;
}
if (!elementName.equalsIgnoreCase(element.getElementName()) || elementType != element.getElementType()
|| !StringUtils.equalsIgnoreCase(namespaceName, namespaceNameElement)) {
return false;
}
}
return true;
}
/**
* Leaves most 'suitable' for current source module elements
*
* @param sourceModule
* @param elements
* @param cache
* Model access cache if available
* @param monitor
* Progress monitor
* @return
*/
public static <T extends IModelElement> Collection<T> filterElements(ISourceModule sourceModule,
@Nullable Collection<T> elements, IModelAccessCache cache, IProgressMonitor monitor) {
if (elements == null) {
return null;
}
if (canUseFileNetworkFilter(elements)) {
return fileNetworkFilter(sourceModule, elements, cache, monitor);
}
return elements;
}
/**
* Returns the current method or function by the specified file and offset
*
* @param sourceModule
* The file where current namespace is requested
* @param offset
* The offset where current namespace is requested
* @return method element, or <code>null</code> if the scope not a method
* scope
*/
@Nullable
public static IMethod getCurrentMethod(ISourceModule sourceModule, int offset) {
try {
IModelElement currentMethod = sourceModule.getElementAt(offset);
while (currentMethod != null) {
if (currentMethod instanceof IMethod) {
return (IMethod) currentMethod;
}
if (!(currentMethod instanceof IField)) {
break;
}
currentMethod = currentMethod.getParent();
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
return null;
}
/**
* Returns the current namespace by the specified model element
*
* @param element
* Model element
* @return namespace element, or <code>null</code> if the scope is global
* under the specified cursor position
*/
@Nullable
public static IType getCurrentNamespace(IModelElement element) {
try {
IModelElement currentNs = element;
while (currentNs != null) {
if (currentNs instanceof IType && PHPFlags.isNamespace(((IType) currentNs).getFlags())) {
return (IType) currentNs;
}
currentNs = currentNs.getParent();
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
return null;
}
/**
* Returns the current namespace by the specified file and offset
*
* @param sourceModule
* The file where current namespace is requested
* @param sourceModule
* The offset where current namespace is requested
* @return namespace element, or <code>null</code> if the scope is global
* under the specified cursor position
*/
@Nullable
public static IType getCurrentNamespace(ISourceModule sourceModule, int offset) {
try {
IModelElement currentNs = sourceModule.getElementAt(offset);
while (currentNs instanceof IField) {
currentNs = sourceModule.getElementAt(((IField) currentNs).getSourceRange().getOffset() - 1);
}
while (currentNs != null) {
if (currentNs instanceof IType && PHPFlags.isNamespace(((IType) currentNs).getFlags())) {
return (IType) currentNs;
}
currentNs = currentNs.getParent();
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
return null;
}
@Nullable
public static IType getCurrentNamespaceIfAny(ISourceModule sourceModule, int offset) {
IType result = getCurrentNamespace(sourceModule, offset);
if (result == null) {
try {
IModelElement[] elements = sourceModule.getChildren();
for (IModelElement modelElement : elements) {
if (modelElement instanceof IType && PHPFlags.isNamespace(((IType) modelElement).getFlags())) {
result = (IType) modelElement;
}
if (modelElement instanceof SourceRefElement) {
SourceRefElement child = (SourceRefElement) modelElement;
ISourceRange range = child.getSourceRange();
int start = range.getOffset();
int end = start + range.getLength();
if (start <= offset && offset <= end) {
return result;
}
}
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
}
return result;
}
/**
* if there are error in the php file,the parser can not be parse the ast
* correctly
*
* @param sourceModule
* @param offset
* @return
*/
@Nullable
public static IType getPossibleCurrentNamespace(ISourceModule sourceModule, int offset) {
try {
IType result = getCurrentNamespace(sourceModule, offset);
if (result == null) {
IType[] types = sourceModule.getTypes();
if (types != null && types.length > 0 && PHPFlags.isNamespace(types[0].getFlags())) {
for (int i = 0; i < types.length; i++) {
if (types[i].getSourceRange().getOffset() <= offset
&& PHPFlags.isNamespace(types[i].getFlags())) {
result = types[i];
} else {
return result;
}
}
}
}
return result;
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
return null;
}
/**
* Returns the current class or interface by the specified file and offset
*
* @param sourceModule
* The file where current namespace is requested
* @param offset
* The offset where current namespace is requested
* @return type element, or <code>null</code> if the scope not a class or
* interface scope
*/
@Nullable
public static IType getCurrentType(IModelElement element) {
try {
while (element != null) {
if (element instanceof IType) {
if (!PHPFlags.isNamespace(((IType) element).getFlags())) {
return (IType) element;
}
break;
}
element = element.getParent();
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
return null;
}
/**
* Returns the current class or interface by the specified file and offset
*
* @param sourceModule
* The file where current namespace is requested
* @param offset
* The offset where current namespace is requested
* @return type element, or <code>null</code> if the scope not a class or
* interface scope
*/
@Nullable
public static IType getCurrentType(ISourceModule sourceModule, int offset) {
try {
return getCurrentType(sourceModule.getElementAt(offset));
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
return null;
}
/**
* Returns PHPDoc block associated with the given IField element
*
* @param field
* @return
*/
@Nullable
public static PHPDocBlock getDocBlock(IField field) {
if (field == null) {
return null;
}
try {
ISourceModule sourceModule = field.getSourceModule();
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
ASTNode fieldDeclaration = PHPModelUtils.getNodeByField(moduleDeclaration, field);
if (fieldDeclaration instanceof IPHPDocAwareDeclaration) {
return ((IPHPDocAwareDeclaration) fieldDeclaration).getPHPDoc();
} else if (fieldDeclaration == null) {
return DefineMethodUtils.getDefinePHPDocBlockByField(moduleDeclaration, field);
}
} catch (ModelException e) {
if (DLTKCore.DEBUG) {
Logger.logException(e);
}
}
return null;
}
/**
* Returns PHPDoc block associated with the given IMethod element
*
* @param method
* @return
*/
@Nullable
public static PHPDocBlock getDocBlock(IMethod method) {
if (method == null) {
return null;
}
try {
ISourceModule sourceModule = method.getSourceModule();
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
MethodDeclaration methodDeclaration = PHPModelUtils.getNodeByMethod(moduleDeclaration, method);
if (methodDeclaration instanceof IPHPDocAwareDeclaration) {
return ((IPHPDocAwareDeclaration) methodDeclaration).getPHPDoc();
}
} catch (ModelException e) {
if (DLTKCore.DEBUG) {
Logger.logException(e);
}
}
return null;
}
/**
* Returns PHPDoc block associated with the given IType element
*
* @param type
* @return
*/
@Nullable
public static PHPDocBlock getDocBlock(IType type) {
if (type == null) {
return null;
}
try {
ISourceModule sourceModule = type.getSourceModule();
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
TypeDeclaration typeDeclaration = PHPModelUtils.getNodeByClass(moduleDeclaration, type);
if (typeDeclaration instanceof IPHPDocAwareDeclaration) {
return ((IPHPDocAwareDeclaration) typeDeclaration).getPHPDoc();
}
} catch (ModelException e) {
if (DLTKCore.DEBUG) {
Logger.logException(e);
}
}
return null;
}
/**
* This method returns field corresponding to its name and the file where it
* was referenced. The field name may contain also the namespace part, like:
* A\B\C or \A\B\C
*
* @param fieldName
* Tye fully qualified field name
* @param sourceModule
* The file where the element is referenced
* @param offset
* The offset where the element is referenced
* @param monitor
* Progress monitor
* @return a list of relevant IField elements
* @throws ModelException
*/
@NonNull
public static IField[] getFields(String fieldName, ISourceModule sourceModule, int offset, IProgressMonitor monitor)
throws ModelException {
return getFields(fieldName, sourceModule, offset, null, monitor);
}
/**
* This method returns field corresponding to its name and the file where it
* was referenced. The field name may contain also the namespace part, like:
* A\B\C or \A\B\C
*
* @param fieldName
* Tye fully qualified field name
* @param sourceModule
* The file where the element is referenced
* @param offset
* The offset where the element is referenced
* @param cache
* Model access cache if available
* @param monitor
* Progress monitor
* @return a list of relevant IField elements
* @throws ModelException
*/
@NonNull
public static IField[] getFields(String fieldName, ISourceModule sourceModule, int offset, IModelAccessCache cache,
IProgressMonitor monitor) throws ModelException {
if (fieldName == null || fieldName.length() == 0) {
return PHPModelAccess.NULL_FIELDS;
}
if (!fieldName.startsWith("$")) { // variables are not //$NON-NLS-1$
// supported by
// namespaces in PHP 5.3
String namespace = extractNamespaceName(fieldName, sourceModule, offset);
fieldName = extractElementName(fieldName);
if (namespace != null) {
if (namespace.length() > 0) {
IField[] fields = getNamespaceField(namespace, fieldName, true, sourceModule, cache, monitor);
if (fields.length > 0) {
return fields;
}
return PHPModelAccess.NULL_FIELDS;
}
// it's a global reference: \C
} else {
// look for the element in current namespace:
IType currentNamespace = getCurrentNamespace(sourceModule, offset);
if (currentNamespace != null) {
namespace = currentNamespace.getElementName();
IField[] fields = getNamespaceField(namespace, fieldName, true, sourceModule, cache, monitor);
if (fields.length > 0) {
return fields;
}
// For functions and constants, PHP will fall back to global
// functions or constants if a namespaced function or
// constant does not exist:
IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject());
fields = PHPModelAccess.getDefault().findFields(fieldName, MatchRule.EXACT,
Modifiers.AccConstant | Modifiers.AccGlobal, 0, scope, null);
Collection<IField> filteredElements = filterElements(sourceModule, Arrays.asList(fields), cache,
monitor);
return filteredElements.toArray(new IField[filteredElements.size()]);
}
}
}
IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject());
IField[] fields = PHPModelAccess.getDefault().findFields(fieldName, MatchRule.EXACT, Modifiers.AccGlobal, 0,
scope, null);
Collection<IField> filteredElements = null;
if (fields != null) {
filteredElements = filterElements(sourceModule, Arrays.asList(fields), cache, monitor);
return filteredElements.toArray(new IField[filteredElements.size()]);
}
return PHPModelAccess.NULL_FIELDS;
}
/**
* This method returns function corresponding to its name and the file where
* it was referenced. The function name may contain also the namespace part,
* like: A\B\foo() or \A\B\foo()
*
* @param functionName
* The fully qualified function name
* @param sourceModule
* The file where the element is referenced
* @param offset
* The offset where the element is referenced
* @param monitor
* Progress monitor
* @return a list of relevant IMethod elements
* @throws ModelException
*/
@NonNull
public static IMethod[] getFunctions(String functionName, ISourceModule sourceModule, int offset,
IProgressMonitor monitor) throws ModelException {
return getFunctions(functionName, sourceModule, offset, null, monitor);
}
/**
* This method returns function corresponding to its name and the file where
* it was referenced. The function name may contain also the namespace part,
* like: A\B\foo() or \A\B\foo()
*
* @param functionName
* The fully qualified function name
* @param sourceModule
* The file where the element is referenced
* @param offset
* The offset where the element is referenced
* @param cache
* Temporary model cache instance
* @param monitor
* Progress monitor
* @return a list of relevant IMethod elements
* @throws ModelException
*/
@NonNull
public static IMethod[] getFunctions(String functionName, ISourceModule sourceModule, int offset,
IModelAccessCache cache, IProgressMonitor monitor) throws ModelException {
if (functionName == null || functionName.length() == 0) {
return PHPModelAccess.NULL_METHODS;
}
String namespace = extractNamespaceName(functionName, sourceModule, offset);
functionName = extractElementName(functionName);
if (namespace != null) {
if (namespace.length() > 0) {
IMethod[] functions = getNamespaceFunction(namespace, functionName, true, sourceModule, cache, monitor);
if (functions.length > 0) {
return functions;
}
return PHPModelAccess.NULL_METHODS;
}
// it's a global reference: \foo()
} else {
// look for the element in current namespace:
IType currentNamespace = getCurrentNamespace(sourceModule, offset);
if (currentNamespace != null) {
namespace = currentNamespace.getElementName();
IMethod[] functions = getNamespaceFunction(namespace, functionName, true, sourceModule, cache, monitor);
if (functions.length > 0) {
return functions;
}
// For functions and constants, PHP will fall back to global
// functions or constants if a namespaced function or constant
// does not exist:
return getGlobalFunctions(sourceModule, functionName, cache, monitor);
}
}
return getGlobalFunctions(sourceModule, functionName, cache, monitor);
};
@NonNull
private static IMethod[] getGlobalFunctions(ISourceModule sourceModule, String functionName,
IModelAccessCache cache, IProgressMonitor monitor) throws ModelException {
if (cache != null) {
Collection<IMethod> functions = cache.getGlobalFunctions(sourceModule, functionName, monitor);
if (functions == null) {
return PHPModelAccess.NULL_METHODS;
}
functions = filterTrueGlobal(functions);
return functions.toArray(new IMethod[functions.size()]);
}
IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject());
IMethod[] functions = PHPModelAccess.getDefault().findMethods(functionName, MatchRule.EXACT,
Modifiers.AccGlobal, 0, scope, null);
Collection<IMethod> filteredElements = filterElements(sourceModule, filterTrueGlobal(Arrays.asList(functions)),
null, monitor);
return filteredElements.toArray(new IMethod[filteredElements.size()]);
}
@NonNull
private static Collection<IMethod> filterTrueGlobal(Collection<IMethod> functions) {
List<IMethod> result = new ArrayList<IMethod>();
for (IMethod method : functions) {
if (method.getParent().getElementType() != IModelElement.TYPE) {
result.add(method);
}
}
return result;
}
/**
* This method searches for all fields that where declared in the specified
* method (including global variables that where introduced to this method
* using 'global' keyword)
*
* @param method
* Method to look at
* @param prefix
* Field name
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
*/
@NonNull
public static IModelElement[] getMethodFields(final IMethod method, final String prefix, final boolean exactName,
IProgressMonitor monitor) {
final List<IField> elements = new LinkedList<IField>();
final Set<String> processedVars = new HashSet<String>();
try {
getMethodFields(method, prefix, exactName, elements, processedVars);
// collect global variables
ModuleDeclaration rootNode = SourceParserUtil.getModuleDeclaration(method.getSourceModule());
MethodDeclaration methodDeclaration = PHPModelUtils.getNodeByMethod(rootNode, method);
if (methodDeclaration != null) {
methodDeclaration.traverse(new ASTVisitor() {
public boolean visit(Statement s) throws Exception {
if (s instanceof GlobalStatement) {
GlobalStatement globalStatement = (GlobalStatement) s;
for (Expression e : globalStatement.getVariables()) {
if (e instanceof VariableReference) {
VariableReference varReference = (VariableReference) e;
String varName = varReference.getName();
if (!processedVars.contains(varName)
&& (exactName && varName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(varName, prefix))) {
elements.add(new FakeField((ModelElement) method, varName, e.sourceStart(),
e.sourceEnd() - e.sourceStart()));
processedVars.add(varName);
}
}
}
}
return super.visit(s);
}
});
}
} catch (Exception e) {
PHPCorePlugin.log(e);
}
return elements.toArray(new IModelElement[elements.size()]);
}
public static void getMethodFields(final IMethod method, final String prefix, final boolean exactName,
final List<IField> elements, final Set<String> processedVars) throws ModelException {
IModelElement[] children = method.getChildren();
for (IModelElement child : children) {
if (child.getElementType() == IModelElement.FIELD) {
String elementName = child.getElementName();
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
IField field = (IField) child;
if (!isSameFileExisting(elements, field)) {
elements.add((IField) child);
processedVars.add(elementName);
}
}
}
}
if (isNestedAnonymousMethod(method)) {
getMethodFields((IMethod) method.getParent().getParent(), prefix, exactName, elements, processedVars);
}
}
public static boolean isNestedAnonymousMethod(final IMethod method) {
return PHPCoreConstants.ANONYMOUS.equals(method.getElementName()) && method.getParent() instanceof IField
&& method.getParent().getParent() instanceof IMethod;
};
private static boolean isSameFileExisting(List<IField> elements, IField field) {
for (IField current : elements) {
if (isSameField(current, field)) {
return true;
}
}
return false;
}
public static boolean isSameField(IField current, IField field) {
if (!(field instanceof SourceField)) {
return false;
}
if (current == field) {
return true;
}
return current.getElementName().equals(field.getElementName()) && current.getParent() != null
&& current.getParent().equals(field.getParent());
}
/**
* This method searches for all global fields that where declared in the
* specified file.
*
* @param sourceModule
* Source module to look at
* @param prefix
* Field name
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
*/
@NonNull
public static IField[] getFileFields(final ISourceModule sourceModule, final String prefix, final boolean exactName,
IProgressMonitor monitor) {
final List<IField> elements = new LinkedList<IField>();
try {
IField[] sourceModuleFields = sourceModule.getFields();
for (IField field : sourceModuleFields) {
String elementName = field.getElementName();
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
elements.add(field);
}
}
} catch (Exception e) {
PHPCorePlugin.log(e);
}
return elements.toArray(new IField[elements.size()]);
}
/**
* This method returns field declared under specified namespace
*
* @param namespace
* Namespace name
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param sourceModule
* Source module where the field is referenced
* @param monitor
* Progress monitor
* @return field declared in the specified namespace, or null if there is
* none
* @throws ModelException
*/
@NonNull
public static IField[] getNamespaceField(String namespace, String prefix, boolean exactName,
ISourceModule sourceModule, IProgressMonitor monitor) throws ModelException {
return getNamespaceField(namespace, prefix, exactName, sourceModule, null, monitor);
}
/**
* This method returns field declared under specified namespace
*
* @param namespace
* Namespace name
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param sourceModule
* Source module where the field is referenced
* @param cache
* Model access cache if available
* @param monitor
* Progress monitor
* @return field declared in the specified namespace, or null if there is
* none
* @throws ModelException
*/
@NonNull
public static IField[] getNamespaceField(String namespace, String prefix, boolean exactName,
ISourceModule sourceModule, IModelAccessCache cache, IProgressMonitor monitor) throws ModelException {
IType[] namespaces = getNamespaces(sourceModule, namespace, cache, monitor);
Collection<IField> result = new LinkedList<IField>();
for (IType ns : namespaces) {
result.addAll(Arrays.asList(PHPModelUtils.getTypeField(ns, prefix, exactName)));
}
if (cache != null) {
result = cache.filterModelElements(sourceModule, result, monitor);
}
return result.toArray(new IField[result.size()]);
}
/**
* This method returns method declared under specified namespace
*
* @param namespace
* Namespace name
* @param prefix
* Function name or prefix
* @param exactName
* Whether the type name is exact or it is prefix
* @param sourceModule
* Source module where the function is referenced
* @param monitor
* Progress monitor
* @throws ModelException
*/
@NonNull
public static IMethod[] getNamespaceFunction(String namespace, String prefix, boolean exactName,
ISourceModule sourceModule, IProgressMonitor monitor) throws ModelException {
return getNamespaceFunction(namespace, prefix, exactName, sourceModule, null, monitor);
}
/**
* This method returns method declared under specified namespace
*
* @param namespace
* Namespace name
* @param prefix
* Function name or prefix
* @param exactName
* Whether the type name is exact or it is prefix
* @param sourceModule
* Source module where the function is referenced
* @param cache
* Model access cache if available
* @param monitor
* Progress monitor
* @throws ModelException
*/
@NonNull
public static IMethod[] getNamespaceFunction(String namespace, String prefix, boolean exactName,
ISourceModule sourceModule, IModelAccessCache cache, IProgressMonitor monitor) throws ModelException {
IType[] namespaces = getNamespaces(sourceModule, namespace, cache, monitor);
Collection<IMethod> result = new LinkedList<IMethod>();
for (IType ns : namespaces) {
result.addAll(Arrays.asList(PHPModelUtils.getTypeMethod(ns, prefix, exactName)));
}
if (cache != null) {
result = cache.filterModelElements(sourceModule, result, monitor);
}
return result.toArray(new IMethod[result.size()]);
}
/**
* Guess the namespace where the specified element is declared.
*
* @param elementName
* The name of the element, like: \A\B, A\B, namespace\B, \B,
* etc...
* @param sourceModule
* Source module where the element is referenced
* @param offset
* The offset in file where the element is referenced
* @param monitor
* @return model elements of found namespace, otherwise <code>null</code>
* (global namespace)
* @throws ModelException
*/
@NonNull
public static IType[] getNamespaceOf(String elementName, ISourceModule sourceModule, int offset,
IProgressMonitor monitor) throws ModelException {
return getNamespaceOf(elementName, sourceModule, offset, null, monitor);
}
/**
* Guess the namespace where the specified element is declared.
*
* @param elementName
* The name of the element, like: \A\B, A\B, namespace\B, \B,
* etc...
* @param sourceModule
* Source module where the element is referenced
* @param offset
* The offset in file where the element is referenced
* @param cache
* Model access cache if available
* @param monitor
* @return model elements of found namespace, otherwise <code>null</code>
* (global namespace)
* @throws ModelException
*/
@NonNull
public static IType[] getNamespaceOf(String elementName, ISourceModule sourceModule, int offset,
IModelAccessCache cache, IProgressMonitor monitor) throws ModelException {
String namespace = extractNamespaceName(elementName, sourceModule, offset);
if (namespace != null && namespace.length() > 0) {
IType[] namespaces = getNamespaces(sourceModule, namespace, cache, monitor);
if (cache != null) {
Collection<IType> result = Arrays.asList(namespaces);
result = cache.filterModelElements(sourceModule, result, monitor);
return result.toArray(new IType[result.size()]);
}
return namespaces;
}
return PHPModelAccess.NULL_TYPES;
}
/**
* This method returns type declared under specified namespace
*
* @param namespace
* Namespace name
* @param prefix
* Type name or prefix
* @param exactName
* Whether the type name is exact or it is prefix
* @param sourceModule
* Source module where the type is referenced
* @param monitor
* Progress monitor
* @return type declared in the specified namespace, or null if there is
* none
* @throws ModelException
*/
@NonNull
public static IType[] getNamespaceType(String namespace, String prefix, boolean exactName,
ISourceModule sourceModule, IProgressMonitor monitor, boolean isType) throws ModelException {
return getNamespaceType(namespace, prefix, exactName, sourceModule, null, monitor, isType);
}
/**
* This method returns type declared under specified namespace
*
* @param namespace
* Namespace name
* @param prefix
* Type name or prefix
* @param exactName
* Whether the type name is exact or it is prefix
* @param sourceModule
* Source module where the type is referenced
* @param cache
* Model access cache if available
* @param monitor
* Progress monitor
* @param isType
* @return type declared in the specified namespace, or null if there is
* none
* @throws ModelException
*/
@NonNull
public static IType[] getNamespaceType(String namespace, String prefix, boolean exactName,
ISourceModule sourceModule, IModelAccessCache cache, IProgressMonitor monitor, boolean isType)
throws ModelException {
IType[] namespaces = getNamespaces(sourceModule, namespace, cache, monitor);
Collection<IType> result = new LinkedList<IType>();
for (IType ns : namespaces) {
result.addAll(Arrays.asList(PHPModelUtils.getTypeType(ns, prefix, exactName, isType)));
}
if (cache != null) {
result = cache.filterModelElements(sourceModule, result, monitor);
}
return result.toArray(new IType[result.size()]);
}
@NonNull
private static IType[] getNamespaces(ISourceModule sourceModule, String namespaceName, IModelAccessCache cache,
IProgressMonitor monitor) throws ModelException {
if (cache != null) {
Collection<IType> namespaces = cache.getNamespaces(sourceModule, namespaceName, monitor);
if (namespaces == null) {
return PHPModelAccess.NULL_TYPES;
}
return namespaces.toArray(new IType[namespaces.size()]);
}
IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject());
IType[] namespaces = PHPModelAccess.getDefault().findNamespaces(null, namespaceName, MatchRule.EXACT, 0, 0,
scope, monitor);
return namespaces;
}
public static TypeDeclaration getNodeByClass(ModuleDeclaration rootNode, IType type) throws ModelException {
DeclarationSearcher visitor = new DeclarationSearcher(rootNode, type, DeclarationType.CLASS);
try {
rootNode.traverse(visitor);
} catch (Exception e) {
if (DLTKCore.DEBUG) {
Logger.logException(e);
}
}
return (TypeDeclaration) visitor.getResult();
}
public static ASTNode getNodeByElement(ModuleDeclaration rootNode, IModelElement element) throws ModelException {
switch (element.getElementType()) {
case IModelElement.TYPE:
return getNodeByClass(rootNode, (IType) element);
case IModelElement.METHOD:
return getNodeByMethod(rootNode, (IMethod) element);
case IModelElement.FIELD:
return getNodeByField(rootNode, (IField) element);
default:
throw new IllegalArgumentException("Unsupported element type: " //$NON-NLS-1$
+ element.getClass().getName());
}
}
public static ASTNode getNodeByField(ModuleDeclaration rootNode, IField field) throws ModelException {
DeclarationSearcher visitor = new DeclarationSearcher(rootNode, field, DeclarationType.FIELD);
try {
rootNode.traverse(visitor);
} catch (Exception e) {
if (DLTKCore.DEBUG) {
Logger.logException(e);
}
}
return (ASTNode) visitor.getResult();
}
public static MethodDeclaration getNodeByMethod(ModuleDeclaration rootNode, IMethod method) throws ModelException {
DeclarationSearcher visitor = new DeclarationSearcher(rootNode, method, DeclarationType.METHOD);
try {
rootNode.traverse(visitor);
} catch (Exception e) {
if (DLTKCore.DEBUG) {
Logger.logException(e);
}
}
return (MethodDeclaration) visitor.getResult();
}
/**
* Returns all super classes filtered using file hierarchy
*
* @throws ModelException
*/
@NonNull
public static IType[] getSuperClasses(IType type, ITypeHierarchy hierarchy) throws ModelException {
if (hierarchy == null) {
if (!PHPToolkitUtil.isFromPHPProject(type)) {
return EMPTY_TYPES;
} else {
hierarchy = type.newSupertypeHierarchy(null);
}
}
Collection<IType> filtered = filterElements(type.getSourceModule(),
Arrays.asList(hierarchy.getAllSuperclasses(type)), null, null);
return filtered.toArray(new IType[filtered.size()]);
}
/**
* Finds field in the super class hierarchy
*
* @param type
* Class element
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @throws CoreException
*/
@NonNull
public static IField[] getSuperTypeHierarchyField(IType type, String prefix, boolean exactName,
IProgressMonitor monitor) throws CoreException {
return getSuperTypeHierarchyField(type, null, prefix, exactName, monitor);
}
/**
* Finds field in the super class hierarchy
*
* @param type
* Class element
* @param hierarchy
* Cached type hierarchy (<code>null</code> to build a new one)
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @throws CoreException
*/
@NonNull
public static IField[] getSuperTypeHierarchyField(IType type, ITypeHierarchy hierarchy, String prefix,
boolean exactName, IProgressMonitor monitor) throws CoreException {
IType[] allSuperclasses = getSuperClasses(type, hierarchy);
return getTypesField(allSuperclasses, prefix, exactName);
}
/**
* Finds method in the super class hierarchy
*
* @param type
* Class element
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @throws CoreException
*/
@NonNull
public static IMethod[] getSuperTypeHierarchyMethod(IType type, String prefix, boolean exactName,
IProgressMonitor monitor) throws CoreException {
return getSuperTypeHierarchyMethod(type, null, prefix, exactName, monitor);
}
/**
* Finds method in the super class hierarchy
*
* @param type
* Class element
* @param hierarchy
* Cached type hierarchy (<code>null</code> to build a new one)
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @throws CoreException
*/
@NonNull
public static IMethod[] getSuperTypeHierarchyMethod(IType type, ITypeHierarchy hierarchy, String prefix,
boolean exactName, IProgressMonitor monitor) throws CoreException {
IType[] allSuperclasses = getSuperClasses(type, hierarchy);
return getTypesMethod(allSuperclasses, prefix, exactName);
}
/**
* Returns the type field element by name
*
* @param type
* Type
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact name or prefix
* @throws ModelException
*/
@NonNull
public static IField[] getTypeField(IType type, String prefix, boolean exactName) throws ModelException {
List<IField> result = new LinkedList<IField>();
if (type.exists()) {
Set<String> nameSet = new HashSet<String>();
IField[] fields = type.getFields();
for (IField field : fields) {
String elementName = field.getElementName();
if (elementName.startsWith("$")) { //$NON-NLS-1$
nameSet.add(elementName.substring(1));
}
if (elementName.startsWith("$") && !prefix.startsWith("$")) { //$NON-NLS-1$ //$NON-NLS-2$
elementName = elementName.substring(1);
}
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
result.add(field);
}
}
fields = TraitUtils.getTraitFields(type, nameSet);
for (IField field : fields) {
String elementName = field.getElementName();
if (elementName.startsWith("$") && !prefix.startsWith("$")) { //$NON-NLS-1$ //$NON-NLS-2$
elementName = elementName.substring(1);
}
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
result.add(field);
}
}
}
return result.toArray(new IField[result.size()]);
}
/**
* Finds field by name in the class hierarchy
*
* @param type
* Class element
* @param hierarchy
* Cached type hierarchy
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static IField[] getTypeHierarchyField(IType type, ITypeHierarchy hierarchy, String prefix, boolean exactName,
IProgressMonitor monitor) throws CoreException {
if (prefix == null) {
throw new NullPointerException();
}
final List<IField> fields = new LinkedList<IField>();
fields.addAll(Arrays.asList(getTypeField(type, prefix, exactName)));
if (type.getSuperClasses() != null && type.getSuperClasses().length > 0) {
fields.addAll(Arrays.asList(getSuperTypeHierarchyField(type, hierarchy, prefix, exactName, monitor)));
}
return fields.toArray(new IField[fields.size()]);
}
/**
* Finds field by name in the class hierarchy (including the class itself)
*
* @param type
* Class element
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static IField[] getTypeHierarchyField(IType type, String prefix, boolean exactName, IProgressMonitor monitor)
throws CoreException {
return getTypeHierarchyField(type, null, prefix, exactName, monitor);
}
/**
* Finds field documentation by field name in the class hierarchy
*
* @param type
* Class element
* @param name
* Field name
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static PHPDocBlock[] getTypeHierarchyFieldDoc(IType type, String name, boolean exactName,
IProgressMonitor monitor) throws CoreException {
if (name == null) {
throw new NullPointerException();
}
final List<PHPDocBlock> docs = new LinkedList<PHPDocBlock>();
for (IField field : getTypeHierarchyField(type, name, exactName, monitor)) {
PHPDocBlock docBlock = getDocBlock(field);
if (docBlock != null) {
docs.add(docBlock);
}
}
return docs.toArray(new PHPDocBlock[docs.size()]);
}
/**
* Finds method by name in the class hierarchy (including the class itself)
*
* @param type
* Class element
* @param hierarchy
* Cached type hierarchy
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static IMethod[] getTypeHierarchyMethod(IType type, ITypeHierarchy hierarchy, String prefix,
boolean exactName, IProgressMonitor monitor) throws CoreException {
if (prefix == null) {
throw new NullPointerException();
}
final List<IMethod> methods = new LinkedList<IMethod>();
methods.addAll(Arrays.asList(getTypeMethod(type, prefix, exactName)));
if (type.getSuperClasses() != null && type.getSuperClasses().length > 0) {
methods.addAll(Arrays.asList(getSuperTypeHierarchyMethod(type, hierarchy, prefix, exactName, monitor)));
}
return methods.toArray(new IMethod[methods.size()]);
}
/**
* Finds the first method by name in the class hierarchy (including the
* class itself)
*
* @param type
* Class element
* @param hierarchy
* Cached type hierarchy
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static IMethod[] getFirstTypeHierarchyMethod(IType type, ITypeHierarchy hierarchy, String prefix,
boolean exactName, IProgressMonitor monitor) throws CoreException {
if (prefix == null) {
throw new NullPointerException();
}
final List<IMethod> methods = new LinkedList<IMethod>();
methods.addAll(Arrays.asList(getTypeMethod(type, prefix, exactName)));
if (type.getSuperClasses() != null && type.getSuperClasses().length > 0 && methods.size() == 0) {
IType[] allSuperclasses = getSuperClasses(type, hierarchy);
for (IType superClass : allSuperclasses) {
IMethod[] method = getTypeMethod(superClass, prefix, exactName);
if (method != null && method.length > 0) {
methods.addAll(Arrays.asList(method));
break;
}
}
}
return methods.toArray(new IMethod[methods.size()]);
}
/**
* Finds method by name in the class hierarchy (including the class itself)
*
* @param type
* Class element
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static IMethod[] getTypeHierarchyMethod(IType type, String prefix, boolean exactName,
IProgressMonitor monitor) throws CoreException {
return getTypeHierarchyMethod(type, null, prefix, exactName, monitor);
}
@NonNull
public static PHPDocBlock[] getTypeHierarchyMethodDoc(IType type, String prefix, boolean exactName,
IProgressMonitor monitor) throws CoreException {
return getTypeHierarchyMethodDoc(type, null, prefix, exactName, monitor);
}
/**
* Finds method documentation by method name in the class hierarchy
*
* @param type
* Class element
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param monitor
* Progress monitor
* @throws CoreException
*/
@NonNull
public static PHPDocBlock[] getTypeHierarchyMethodDoc(IType type, ITypeHierarchy hierarchy, String prefix,
boolean exactName, IProgressMonitor monitor) throws CoreException {
if (prefix == null) {
throw new NullPointerException();
}
final List<PHPDocBlock> docs = new LinkedList<PHPDocBlock>();
for (IMethod method : getTypeHierarchyMethod(type, hierarchy, prefix, exactName, monitor)) {
PHPDocBlock docBlock = getDocBlock(method);
if (docBlock != null) {
docs.add(docBlock);
}
}
return docs.toArray(new PHPDocBlock[docs.size()]);
}
/**
* Returns the type method element by name
*
* @param type
* Type
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact name or prefix
* @throws ModelException
*/
@NonNull
public static IMethod[] getTypeMethod(IType type, String prefix, boolean exactName) throws ModelException {
List<IMethod> result = new LinkedList<IMethod>();
if (type.exists()) {
Set<String> nameSet = new HashSet<String>();
IMethod[] methods = type.getMethods();
for (IMethod method : methods) {
String elementName = method.getElementName();
nameSet.add(elementName);
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
result.add(method);
}
}
methods = TraitUtils.getTraitMethods(type, nameSet);
for (IMethod method : methods) {
String elementName = method.getElementName();
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
result.add(method);
}
}
}
return result.toArray(new IMethod[result.size()]);
}
/**
* This method returns type corresponding to its name and the file where it
* was referenced. The type name may contain also the namespace part, like:
* A\B\C or \A\B\C
*
* @param typeName
* Tye fully qualified type name
* @param sourceModule
* The file where the element is referenced
* @param offset
* The offset where the element is referenced
* @param monitor
* Progress monitor
* @return a list of relevant IType elements
* @throws ModelException
*/
@NonNull
public static IType[] getTypes(String typeName, ISourceModule sourceModule, int offset, IProgressMonitor monitor)
throws ModelException {
return getTypes(typeName, sourceModule, offset, null, monitor);
}
/**
* This method returns type corresponding to its name and the file where it
* was referenced. The type name may contain also the namespace part, like:
* A\B\C or \A\B\C
*
* @param typeName
* Tye fully qualified type name
* @param sourceModule
* The file where the element is referenced
* @param offset
* The offset where the element is referenced
* @param cache
* Model access cache if available
* @param monitor
* Progress monitor
* @return a list of relevant IType elements
* @throws ModelException
*/
@NonNull
public static IType[] getTypes(String typeName, ISourceModule sourceModule, int offset, IModelAccessCache cache,
IProgressMonitor monitor) throws ModelException {
return getTypes(typeName, sourceModule, offset, cache, monitor, true, false);
}
@NonNull
public static IType[] getTypes(String typeName, ISourceModule sourceModule, int offset, IModelAccessCache cache,
IProgressMonitor monitor, boolean isType) throws ModelException {
return getTypes(typeName, sourceModule, offset, cache, monitor, isType, false);
}
@NonNull
public static IType[] getTypes(String typeName, ISourceModule sourceModule, int offset, IModelAccessCache cache,
IProgressMonitor monitor, boolean isType, boolean isGlobal) throws ModelException {
if (typeName == null || typeName.length() == 0) {
return PHPModelAccess.NULL_TYPES;
}
if (!isGlobal) {
String fullTypeName = typeName;
String namespace = extractNamespaceName(typeName, sourceModule, offset);
typeName = extractElementName(typeName);
if (namespace != null) {
if (namespace.length() > 0) {
String typeNameSpaceName = extractNameSpaceName(fullTypeName);
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=515844
// only look for aliases when there is no namespace
// separator in the type name
if (typeNameSpaceName == null) {
typeName = getRealName(typeName, sourceModule, offset, typeName);
}
IType[] types = getNamespaceType(namespace, typeName, true, sourceModule, cache, monitor, isType);
if (types.length > 0) {
return types;
}
return PHPModelAccess.NULL_TYPES;
}
// it's a global reference: \A
} else {
// look for the element in current namespace:
IType currentNamespace = getCurrentNamespace(sourceModule, offset);
if (currentNamespace != null) {
namespace = currentNamespace.getElementName();
IType[] types = getNamespaceType(namespace, typeName, true, sourceModule, cache, monitor, isType);
if (types.length > 0) {
return types;
}
}
}
}
Collection<IType> types;
if (cache == null) {
IDLTKSearchScope scope = SearchEngine.createSearchScope(sourceModule.getScriptProject());
if (isType) {
types = Arrays
.asList(PHPModelAccess.getDefault().findTypes(typeName, MatchRule.EXACT, 0, 0, scope, null));
} else {
types = Arrays
.asList(PHPModelAccess.getDefault().findTraits(typeName, MatchRule.EXACT, 0, 0, scope, null));
}
} else {
// Cached types will already be filtered by method
// PHPModelUtils.filterElements() when cache is an instance of
// PerFileModelAccessCache. The filtering is too early here but it's
// ok so long it doesn't change the results of bottom
// getCurrentNamespace(type) and filterElements(sourceModule,
// result, null, monitor) filtering for types without namespace.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=497003 and
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=496530
if (isType) {
types = cache.getTypes(sourceModule, typeName, null, monitor);
} else {
types = cache.getTraits(sourceModule, typeName, null, monitor);
}
if (types == null) {
return PHPModelAccess.NULL_TYPES;
}
}
List<IType> result = new ArrayList<IType>();
for (IType type : types) {
if (getCurrentNamespace(type) == null) {
result.add(type);
}
}
types = filterElements(sourceModule, result, null, monitor);
return result.toArray(new IType[result.size()]);
}
@NonNull
public static IType[] getTraits(String typeName, ISourceModule sourceModule, int offset, IModelAccessCache cache,
IProgressMonitor monitor) throws ModelException {
return getTypes(typeName, sourceModule, offset, cache, monitor, false, false);
}
/**
* Finds field in the list of given types
*
* @param types
* List of types
* @param prefix
* Field name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @throws ModelException
*/
@NonNull
public static IField[] getTypesField(IType[] types, String prefix, boolean exactName) throws ModelException {
List<IField> result = new LinkedList<IField>();
for (IType type : types) {
result.addAll(Arrays.asList(getTypeField(type, prefix, exactName)));
}
return result.toArray(new IField[result.size()]);
}
/**
* Finds method in the list of given types
*
* @param types
* List of types
* @param prefix
* Method name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @throws ModelException
*/
@NonNull
public static IMethod[] getTypesMethod(IType[] types, String prefix, boolean exactName) throws ModelException {
List<IMethod> result = new LinkedList<IMethod>();
for (IType type : types) {
result.addAll(Arrays.asList(getTypeMethod(type, prefix, exactName)));
}
return result.toArray(new IMethod[result.size()]);
}
/**
* Returns the type inner type element by name
*
* @param type
* Type
* @param prefix
* Enclosed type name or prefix
* @param exactName
* Whether the name is exact or it is prefix
* @param isType
* @throws ModelException
*/
@NonNull
public static IType[] getTypeType(IType type, String prefix, boolean exactName, boolean isType)
throws ModelException {
List<IType> result = new LinkedList<IType>();
IType[] types = type.getTypes();
for (IType t : types) {
if (isType && PHPFlags.isTrait(t.getFlags()) || !isType && !PHPFlags.isTrait(t.getFlags())) {
continue;
}
String elementName = t.getElementName();
if (exactName && elementName.equalsIgnoreCase(prefix)
|| !exactName && startsWithIgnoreCase(elementName, prefix)) {
result.add(t);
}
}
return result.toArray(new IType[result.size()]);
}
@NonNull
public static IType[] getTypeType(IType type, String prefix, boolean exactName) throws ModelException {
return getTypeType(type, prefix, exactName, true);
}
/**
* Returns methods that must be overridden in first non-abstract class in
* hierarchy.
*
* @param type
* Type to start the search from
* @param monitor
* Progress monitor
* @return unimplemented methods
* @throws ModelException
*/
@NonNull
public static IMethod[] getUnimplementedMethods(IType type, IProgressMonitor monitor) throws ModelException {
return getUnimplementedMethods(type, null, monitor);
}
/**
* Returns methods that must be overridden in first non-abstract class in
* hierarchy.
*
* @param type
* Type to start the search from
* @param cache
* Temporary model cache instance
* @param monitor
* Progress monitor
* @return unimplemented methods
* @throws ModelException
*/
@NonNull
public static IMethod[] getUnimplementedMethods(IType type, IModelAccessCache cache, IProgressMonitor monitor)
throws ModelException {
HashMap<String, IMethod> abstractMethods = new HashMap<String, IMethod>();
HashSet<String> nonAbstractMethods = new HashSet<String>();
internalGetUnimplementedMethods(type, nonAbstractMethods, abstractMethods, new HashSet<String>(), cache,
monitor, true);
for (String methodName : nonAbstractMethods) {
Iterator<Map.Entry<String, IMethod>> iterator = abstractMethods.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, IMethod> entry = iterator.next();
if (methodName.equalsIgnoreCase(entry.getKey())) {
iterator.remove();
}
}
}
Collection<IMethod> unimplementedMethods = abstractMethods.values();
return unimplementedMethods.toArray(new IMethod[unimplementedMethods.size()]);
}
private static void internalGetUnimplementedMethods(IType type, HashSet<String> nonAbstractMethods,
HashMap<String, IMethod> abstractMethods, Set<String> processedTypes, IModelAccessCache cache,
IProgressMonitor monitor, boolean checkConstructor) throws ModelException {
int typeFlags = type.getFlags();
IMethod[] methods = getTypeMethod(type, "", false); //$NON-NLS-1$
for (IMethod method : methods) {
String methodName = method.getElementName();
int methodFlags = method.getFlags();
boolean isAbstract = PHPFlags.isAbstract(methodFlags);
if (/* !PHPFlags.isInterface(typeFlags)&& */isConstructor(method)) {
if (checkConstructor) {
checkConstructor = false;
} else {
continue;
}
}
if (isAbstract || PHPFlags.isInterface(typeFlags)) {
if (!abstractMethods.containsKey(methodName)) {
abstractMethods.put(methodName, method);
}
} else if (!isAbstract) {
nonAbstractMethods.add(methodName);
}
}
String[] superClasses = type.getSuperClasses();
if (superClasses != null) {
for (String superClass : superClasses) {
if (!processedTypes.add(superClass)) {
continue;
}
Collection<IType> types = null;
if (cache == null) {
IDLTKSearchScope scope = SearchEngine.createSearchScope(type.getScriptProject());
IType[] superTypes = PHPModelAccess.getDefault().findTypes(superClass, MatchRule.EXACT, 0,
Modifiers.AccNameSpace, scope, null);
types = fileNetworkFilter(type.getSourceModule(), Arrays.asList(superTypes), null, monitor);
} else {
String namespaceName = null;
int i = superClass.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (i != -1) {
namespaceName = superClass.substring(0, i);
superClass = superClass.substring(i + 1);
}
types = cache.getClassesOrInterfaces(type.getSourceModule(), superClass, namespaceName, monitor);
}
if (types != null) {
for (IType superType : types) {
internalGetUnimplementedMethods(superType, nonAbstractMethods, abstractMethods, processedTypes,
cache, monitor, checkConstructor);
}
}
}
}
}
public static boolean isConstructor(IMethod method) {
String methodName = method.getElementName();
if (methodName.equals("__construct") //$NON-NLS-1$
|| methodName.equals(method.getDeclaringType().getElementName())) {
return true;
}
return false;
}
/**
*
* @param type
* the given type
* @return if the given type has static member
*/
public static boolean hasStaticOrConstMember(IType type) {
try {
if (PHPFlags.isNamespace(type.getFlags())) {
return false;
}
if (PHPVersion.PHP5_4.isLessThan(ProjectOptions.getPHPVersion(type))) {
return true; // class constant always available
}
ITypeHierarchy hierarchy = type.newSupertypeHierarchy(null);
IModelElement[] members = PHPModelUtils.getTypeHierarchyField(type, hierarchy, "", false, null); //$NON-NLS-1$
if (hasStaticOrConstMember(members)) {
return true;
}
members = PHPModelUtils.getTypeHierarchyMethod(type, hierarchy, "", //$NON-NLS-1$
false, null);
if (hasStaticOrConstMember(members)) {
return true;
}
} catch (ModelException e) {
PHPCorePlugin.log(e);
} catch (CoreException e) {
PHPCorePlugin.log(e);
}
return false;
}
public static boolean hasStaticOrConstMember(IModelElement[] elements) throws ModelException {
for (int i = 0; i < elements.length; i++) {
IModelElement modelElement = elements[i];
if (modelElement instanceof IMember) {
IMember member = (IMember) modelElement;
int flags = member.getFlags();
if (Flags.isStatic(flags) || ((modelElement instanceof IField) && Flags.isFinal(flags))) {
return true;
}
}
}
return false;
}
@NonNull
public static Map<String, UsePart> getAliasToNSMap(final String prefix, ModuleDeclaration moduleDeclaration,
final int offset, IType namespace, final boolean exactMatch) {
final Map<String, UsePart> result = new HashMap<String, UsePart>();
try {
int start = 0;
if (namespace != null) {
start = namespace.getSourceRange().getOffset();
}
final int searchStart = start;
moduleDeclaration.traverse(new ASTVisitor() {
public boolean visit(Statement s) throws Exception {
if (s instanceof UseStatement) {
UseStatement useStatement = (UseStatement) s;
for (UsePart usePart : useStatement.getParts()) {
if (usePart.getAlias() != null && usePart.getAlias().getName() != null) {
String name = usePart.getAlias().getName();
if (startsWithIgnoreCase(name, prefix)) {
result.put(name, usePart);
}
} else {
String name = usePart.getNamespace().getFullyQualifiedName();
int index = name.lastIndexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (index >= 0) {
name = name.substring(index + 1);
}
if (exactMatch && name.equals(prefix) || !exactMatch && name.startsWith(prefix)) {
result.put(name, usePart);
}
}
}
}
return visitGeneral(s);
}
public boolean visitGeneral(ASTNode node) throws Exception {
if (node.sourceStart() > offset || node.sourceEnd() < searchStart) {
return false;
}
return super.visitGeneral(node);
}
});
} catch (Exception e) {
Logger.logException(e);
}
return result;
}
@Nullable
public static String getClassNameForNewStatement(TextSequence newClassStatementText, PHPVersion phpVersion) {
if (phpVersion.isGreaterThan(PHPVersion.PHP5_3)) {
// TextSequence newClassStatementText =
// statementText.subTextSequence(
// functionNameStart + 1, propertyEndPosition - 1);
String newClassName = newClassStatementText.toString().trim();
if (newClassName.startsWith("new") && newClassName.endsWith(")")) { //$NON-NLS-1$ //$NON-NLS-2$
int newClassNameEnd = getFunctionNameEndOffset(newClassStatementText,
newClassStatementText.length() - 1);
int newClassNameStart = PHPTextSequenceUtilities.readIdentifierStartIndex(phpVersion,
newClassStatementText, newClassNameEnd, false);
if (newClassNameStart > 3 && newClassNameStart < newClassNameEnd) {// should
// have
// blank
// chars
// after
// 'new'
newClassName = newClassStatementText.subSequence(newClassNameStart, newClassNameEnd).toString();
return newClassName.trim();
}
} else if (newClassName.startsWith("new")) { //$NON-NLS-1$
return newClassName.substring(3).trim();
}
}
return null;
}
/**
* this function searches the sequence from the right closing bracket ")"
* and finding the position of the left "(" the offset has to be the offset
* of the "("
*/
public static int getFunctionNameEndOffset(TextSequence statementText, int offset) {
if (statementText.charAt(offset) != ')') {
return 0;
}
int currChar = offset;
int bracketsNum = 1;
char inStringMode = 0;
while (bracketsNum != 0 && currChar > 0) {
currChar--;
// get the current char
final char charAt = statementText.charAt(currChar);
// if it is string close / open - update state
if (charAt == '\'' || charAt == '"') {
inStringMode = inStringMode == 0 ? charAt : inStringMode == charAt ? 0 : inStringMode;
}
if (inStringMode != 0)
continue;
if (charAt == ')') {
bracketsNum++;
} else if (charAt == '(') {
bracketsNum--;
}
}
return currChar;
}
public static String getFullName(IType declaringType) {
try {
if (PHPFlags.isNamespace(declaringType.getFlags())) {
return declaringType.getElementName();
}
IModelElement parent = declaringType.getParent();
if (parent.getElementType() == IModelElement.SOURCE_MODULE) {
return declaringType.getElementName();
} else if (parent.getElementType() == IModelElement.TYPE) {
int parentFlags = ((IType) parent).getFlags();
if (PHPFlags.isNamespace(parentFlags)
&& parent.getParent().getElementType() == IModelElement.SOURCE_MODULE) {
StringBuilder b = new StringBuilder(parent.getElementName());
b.append(NamespaceReference.NAMESPACE_SEPARATOR);
b.append(declaringType.getElementName());
return b.toString();
}
}
return getFullName(declaringType.getElementName(), declaringType.getSourceModule(),
declaringType.getSourceRange().getOffset());
} catch (ModelException e) {
return declaringType.getElementName();
}
}
@NonNull
public static String getFullName(@NonNull String typeName, ISourceModule sourceModule, final int offset) {
String namespace = extractNamespaceName(typeName, sourceModule, offset);
String elementName = extractElementName(typeName);
if (namespace != null) {
if (namespace.length() > 0) {
elementName = getRealName(elementName, sourceModule, offset, elementName);
elementName = namespace + NamespaceReference.NAMESPACE_SEPARATOR + elementName;
}
} else {
// look for the element in current namespace:
IType currentNamespace = getCurrentNamespace(sourceModule, offset);
if (currentNamespace != null) {
namespace = currentNamespace.getElementName();
elementName = namespace + NamespaceReference.NAMESPACE_SEPARATOR + elementName;
}
}
return elementName != null ? elementName : ""; //$NON-NLS-1$
}
@NonNull
public static String getFullName(NamespaceName namespaceName) {
StringBuilder sb = new StringBuilder();
if (namespaceName.isGlobal()) {
sb.append(NamespaceReference.NAMESPACE_SEPARATOR);
}
List<Identifier> segments = namespaceName.segments();
for (Identifier identifier : segments) {
if (sb.length() == 0 && namespaceName.isGlobal()) {
sb.append(NamespaceReference.NAMESPACE_SEPARATOR);
} else if (sb.length() > 0) {
sb.append(NamespaceReference.NAMESPACE_SEPARATOR);
}
sb.append(identifier.getName());
}
return sb.toString();
}
@Nullable
public static String getLineSeparator(IProject project) {
String lineSeparator = null;
if (project != null) {
lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME,
Platform.PREF_LINE_SEPARATOR, null, new IScopeContext[] { new ProjectScope(project) });
}
if (lineSeparator == null) {
lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME,
Platform.PREF_LINE_SEPARATOR, null, new IScopeContext[] { InstanceScope.INSTANCE });
}
if (lineSeparator == null) {
lineSeparator = System.getProperty(Platform.PREF_LINE_SEPARATOR);
}
return lineSeparator;
}
public static boolean isInUseTraitStatement(ModuleDeclaration rootNode, final int offset) {
final boolean[] found = new boolean[1];
found[0] = false;
try {
rootNode.traverse(new PHPASTVisitor() {
public boolean visit(TraitUseStatement s) throws Exception {
if (s.sourceStart() <= offset && s.sourceEnd() >= offset) {
found[0] = true;
}
return false;
}
@Override
public boolean visitGeneral(ASTNode node) throws Exception {
if (node.sourceEnd() < offset || node.sourceStart() > offset) {
return false;
}
return super.visitGeneral(node);
}
});
} catch (Exception e) {
e.printStackTrace();
}
return found[0];
}
/**
* Strips single or double quotes from the start and from the end of the
* given string
*
* @param name
* String
* @return
*/
@NonNull
public static String stripQuotes(String name) {
int len = name.length();
if (len > 1 && (name.charAt(0) == '\'' && name.charAt(len - 1) == '\''
|| name.charAt(0) == '"' && name.charAt(len - 1) == '"')) {
name = name.substring(1, len - 1);
}
return name;
}
public static boolean isQuotesString(String name) {
int len = name.length();
if (len > 1 && (name.charAt(0) == '\'' && name.charAt(len - 1) == '\''
|| name.charAt(0) == '"' && name.charAt(len - 1) == '"')) {
return true;
}
return false;
}
@Nullable
public static IModelElement[] getTypeInString(ISourceModule sourceModule, IRegion wordRegion) {
IModelElement[] elements = null;
ModuleDeclaration parsedUnit = SourceParserUtil.getModuleDeclaration(sourceModule, null);
ASTNode node = ASTUtils.findMinimalNode(parsedUnit, wordRegion.getOffset(),
wordRegion.getOffset() + wordRegion.getLength());
if (node instanceof Scalar) {
Scalar scalar = (Scalar) node;
if (PHPModelUtils.isQuotesString(scalar.getValue()) && scalar.getScalarType() == Scalar.TYPE_STRING) {
try {
elements = PHPModelUtils.getTypes(PHPModelUtils.stripQuotes(scalar.getValue()), sourceModule,
scalar.sourceStart(), null, null);
} catch (Exception e) {
}
}
}
return elements;
}
public static void getMethodLabel(IMethod method, StringBuilder buf) {
try {
buf.append(method.getElementName());
buf.append('(');
final IParameter[] params = method.getParameters();
for (int i = 0, nParams = params.length; i < nParams; i++) {
if (i > 0) {
buf.append(COMMA_STRING);
}
if (params[i].getType() != null) {
buf.append(params[i].getType());
} else {
buf.append(params[i].getName());
}
}
buf.append(')');
} catch (ModelException e) {
PHPCorePlugin.log(e);
}
}
}