/*******************************************************************************
* Copyright (c) 2014, 2015, 2016, 2017 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.evaluators;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.dltk.core.*;
import org.eclipse.dltk.evaluation.types.MultiTypeType;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.php.core.compiler.ast.nodes.NamespaceReference;
import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag;
import org.eclipse.php.core.compiler.ast.nodes.UsePart;
import org.eclipse.php.internal.core.Constants;
import org.eclipse.php.internal.core.Logger;
import org.eclipse.php.internal.core.typeinference.PHPClassType;
import org.eclipse.php.internal.core.typeinference.PHPModelUtils;
import org.eclipse.php.internal.core.typeinference.PHPSimpleTypes;
import org.eclipse.php.internal.core.util.MagicMemberUtil;
public class PHPEvaluationUtils {
public static final String BRACKETS = "[]"; //$NON-NLS-1$
public static final Pattern ARRAY_TYPE_PATTERN = Pattern.compile("array\\[.*\\]", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static final String SELF_RETURN_TYPE = "self"; //$NON-NLS-1$
private static final String STATIC_RETURN_TYPE = "static"; //$NON-NLS-1$
private static final String THIS_RETURN_TYPE = "$this"; //$NON-NLS-1$
public static final String BRACKETS_REGEX = "\\[.*\\]"; //$NON-NLS-1$
// Matches all type separators used by method getArrayType()
public static final Pattern TYPE_DELIMS_PATTERN = Pattern.compile("([,\\[\\]]+)"); //$NON-NLS-1$
private static final IEvaluatedType[] EMPTY_LIST = new IEvaluatedType[0];
// XXX: handle nested array[] types?
public static String extractArrayType(String typeName) {
Matcher m = ARRAY_TYPE_PATTERN.matcher(typeName);
if (m.find()) {
int beginIndex = typeName.indexOf('[') + 1;
int endIndex = typeName.lastIndexOf(']');
if (endIndex != -1) {
return typeName.substring(beginIndex, endIndex);
}
}
return removeArrayBrackets(typeName);
}
public static boolean isArrayType(String typeName) {
if (typeName == null || typeName.isEmpty()) {
return false;
}
Matcher m = ARRAY_TYPE_PATTERN.matcher(typeName);
if (m.find() || (typeName.endsWith(BRACKETS) && typeName.length() > 2)) {
return true;
}
return false;
}
public static IEvaluatedType extractArrayType(String typeName, IType currentNamespace, int offset) {
if (typeName == null || typeName.isEmpty()) {
return null;
}
Matcher m = ARRAY_TYPE_PATTERN.matcher(typeName);
if (m.find()) {
return getArrayType(m.group(), currentNamespace, offset);
} else if (typeName.endsWith(BRACKETS) && typeName.length() > 2) {
return getArrayType(typeName.substring(0, typeName.length() - 2), currentNamespace, offset);
}
return null;
}
public static MultiTypeType getArrayType(String type, IType currentNamespace, int offset) {
int beginIndex = type.indexOf('[') + 1;
int endIndex = type.lastIndexOf(']');
if (endIndex != -1) {
type = type.substring(beginIndex, endIndex);
}
MultiTypeType arrayType = new MultiTypeType();
Matcher m = ARRAY_TYPE_PATTERN.matcher(type);
if (m.find()) {
arrayType.addType(getArrayType(m.group(), currentNamespace, offset));
type = m.replaceAll(""); //$NON-NLS-1$
} else if (type.endsWith(BRACKETS) && type.length() > 2) {
arrayType.addType(getArrayType(type.substring(0, type.length() - 2), currentNamespace, offset));
type = type.replaceAll(Pattern.quote(BRACKETS), ""); //$NON-NLS-1$
}
String[] typeNames = type.split(","); //$NON-NLS-1$
for (String name : typeNames) {
if (!"".equals(name)) { //$NON-NLS-1$
int nsSeparatorIndex = name.indexOf(NamespaceReference.NAMESPACE_SEPARATOR);
if (currentNamespace != null && (nsSeparatorIndex < 0 || nsSeparatorIndex > 0)) {
// check if the first part is an alias, then get the full
// name
// NB: do as in method
// PDTModelUtils#collectParameterTypes(IMethod method)
ModuleDeclaration moduleDeclaration = SourceParserUtil
.getModuleDeclaration(currentNamespace.getSourceModule());
String prefix = name;
if (nsSeparatorIndex > 0) {
prefix = name.substring(0, nsSeparatorIndex);
}
final Map<String, UsePart> result = PHPModelUtils.getAliasToNSMap(prefix, moduleDeclaration, offset,
currentNamespace, true);
if (result.containsKey(prefix)) {
String fullName = result.get(prefix).getNamespace().getFullyQualifiedName();
name = name.replace(prefix, fullName);
if (name.charAt(0) != NamespaceReference.NAMESPACE_SEPARATOR) {
name = NamespaceReference.NAMESPACE_SEPARATOR + name;
}
}
}
arrayType.addType(getEvaluatedType(name, currentNamespace));
}
}
return arrayType;
}
/**
* Resolves the type strings e.g from the @property and @method tag.
*
* e.g DateTime|DateTimeZone
*
* @param variableName
* @param docTag
* @return the types of the given variable
*/
public static Collection<String> getTypeBinding(String name, PHPDocTag docTag) {
String[] split = MagicMemberUtil.WHITESPACE_SEPERATOR.split(docTag.getValue().trim());
if (split.length < 2) {
return Collections.emptyList();
}
if (split[1].equals(name)) {
if (Constants.STATIC.equals(split[0])) {
return Collections.emptyList();
}
return Arrays.asList(split[0].split("\\" //$NON-NLS-1$
+ Constants.TYPE_SEPARATOR_CHAR));
}
if (Constants.STATIC.equals(split[0])) {
split = Arrays.copyOfRange(split, 1, split.length);
if (split.length < 2) {
return Collections.emptyList();
}
}
String substring = split[1];
int parenIndex = split[1].indexOf('('); // $NON-NLS-1$
if (parenIndex != -1) {
substring = substring.substring(0, parenIndex);
}
if (substring.equals(name)) {
return Arrays.asList(split[0].split("\\" //$NON-NLS-1$
+ Constants.TYPE_SEPARATOR_CHAR));
}
return Collections.emptyList();
}
/**
* Creates evaluated type according name and namespace.
*
* @param typeName
* @param currentNamespace
* @return evaluated type
*/
public static IEvaluatedType getEvaluatedType(String typeName, IType currentNamespace) {
if (typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) > 0 && currentNamespace != null) {
typeName = NamespaceReference.NAMESPACE_SEPARATOR + currentNamespace.getElementName()
+ NamespaceReference.NAMESPACE_SEPARATOR + typeName;
}
if (typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) != -1 || currentNamespace == null) {
return new PHPClassType(typeName);
} else {
return new PHPClassType(currentNamespace.getElementName(), typeName);
}
}
public static String removeArrayBrackets(String variableName) {
return variableName.replaceAll(BRACKETS_REGEX, ""); //$NON-NLS-1$
}
private static class ClassFinder implements IModelElementVisitor {
private final String search;
public boolean found = false;
public ClassFinder(String name) {
search = name;
}
@Override
public boolean visit(IModelElement element) {
if (element.getElementType() == IModelElement.TYPE && search.equals(element.getElementName())) {
found = true;
}
return !found;
}
}
/**
* @param typeName
* @param space
* namespace (IType) or file (ISourceModule)
* @param offset
* @param types
* @return
*/
public static IEvaluatedType[] evaluatePHPDocType(String[] typeNames, IModelElement space, int offset,
IType[] types) {
ISourceModule sourceModule = space.getAncestor(ISourceModule.class);
IType currentNamespace = space instanceof IType ? (IType) space : null;
List<IEvaluatedType> res = new LinkedList<>();
for (String typeName : typeNames) {
List<IEvaluatedType> evaluated = new LinkedList<>();
if (StringUtils.isBlank(typeName)) {
continue;
}
IEvaluatedType evaluatedType = extractArrayType(typeName, currentNamespace, offset);
if (evaluatedType != null) {
evaluated.add(evaluatedType);
} else {
if (PHPSimpleTypes.isSimpleType(typeName)) {
ClassFinder classFinder = new ClassFinder(typeName);
try {
space.accept(classFinder);
} catch (ModelException e) {
Logger.logException(e);
}
if (classFinder.found) {
evaluated.add(getEvaluatedType(typeName, currentNamespace));
} else {
evaluated.add(PHPSimpleTypes.fromString(typeName));
}
} else if ((typeName.equals(SELF_RETURN_TYPE) || typeName.equals(THIS_RETURN_TYPE)
|| typeName.equals(STATIC_RETURN_TYPE)) && types != null) {
for (IType t : types) {
IEvaluatedType type = getEvaluatedType(PHPModelUtils.getFullName(t), null);
if (type != null) {
evaluated.add(type);
}
}
} else if (typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) == 0) {
evaluated.add(new PHPClassType(typeName));
} else {
if (currentNamespace != null) {
ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceModule);
if (typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) > 0) {
String prefix = typeName.substring(0,
typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR));
final Map<String, UsePart> result = PHPModelUtils.getAliasToNSMap(prefix, moduleDeclaration,
offset, currentNamespace, true);
if (result.containsKey(prefix)) {
String fullName = result.get(prefix).getNamespace().getFullyQualifiedName();
typeName = typeName.replace(prefix, fullName);
if (typeName.charAt(0) != NamespaceReference.NAMESPACE_SEPARATOR) {
typeName = NamespaceReference.NAMESPACE_SEPARATOR + typeName;
}
}
} else if (typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) < 0) {
String prefix = typeName;
final Map<String, UsePart> result = PHPModelUtils.getAliasToNSMap(prefix, moduleDeclaration,
offset, currentNamespace, true);
if (result.containsKey(prefix)) {
String fullName = result.get(prefix).getNamespace().getFullyQualifiedName();
typeName = fullName;
if (typeName.charAt(0) != NamespaceReference.NAMESPACE_SEPARATOR) {
typeName = NamespaceReference.NAMESPACE_SEPARATOR + typeName;
}
}
}
}
IEvaluatedType type = getEvaluatedType(typeName, currentNamespace);
if (type != null) {
evaluated.add(type);
}
}
}
res.addAll(evaluated);
}
if (res.isEmpty()) {
return EMPTY_LIST;
}
return res.toArray(new IEvaluatedType[res.size()]);
}
public static IEvaluatedType[] evaluatePHPDocType(List<TypeReference> typeNames, IModelElement space, int offset,
IType[] types) {
if (typeNames == null || typeNames.isEmpty()) {
return EMPTY_LIST;
}
String[] tmp = new String[typeNames.size()];
for (int i = 0; i < typeNames.size(); i++) {
tmp[i] = typeNames.get(i).getName();
}
return evaluatePHPDocType(tmp, space, offset, types);
}
}