/* * This file is part of the PDT Extensions eclipse plugin. * * (c) Robert Gruendler <r.gruendler@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ package org.pdtextensions.core.util; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.dltk.ast.declarations.MethodDeclaration; import org.eclipse.dltk.ast.declarations.ModuleDeclaration; import org.eclipse.dltk.ast.references.SimpleReference; import org.eclipse.dltk.ast.references.TypeReference; import org.eclipse.dltk.core.IMethod; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IScriptFolder; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.IType; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.SourceParserUtil; 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.evaluation.types.MultiTypeType; import org.eclipse.dltk.internal.core.util.LRUCache; import org.eclipse.dltk.ti.types.IEvaluatedType; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.internal.core.Logger; import org.eclipse.php.core.compiler.ast.nodes.FullyQualifiedReference; import org.eclipse.php.core.compiler.ast.nodes.NamespaceReference; import org.eclipse.php.core.compiler.ast.nodes.PHPDocBlock; import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag; import org.eclipse.php.core.compiler.ast.nodes.UsePart; import org.eclipse.php.core.compiler.ast.visitor.PHPASTVisitor; import org.eclipse.php.internal.core.index.IPHPDocAwareElement; import org.eclipse.php.internal.core.model.PHPModelAccess; 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.typeinference.TraitAliasObject; import org.eclipse.php.internal.core.typeinference.TraitPrecedenceObject; import org.eclipse.php.internal.core.typeinference.TraitUtils; import org.eclipse.php.internal.core.typeinference.UseTrait; import org.pdtextensions.core.PEXCorePlugin; /** * * PHP Extension Utility class for the PHP model. * * @author Robert Gruendler <r.gruendler@gmail.com> * */ @SuppressWarnings("restriction") public class PDTModelUtils { public final static String BACK_SLASH = "\\"; //$NON-NLS-1$ private final static Pattern ARRAY_TYPE_PATTERN = Pattern .compile("array\\[.*\\]"); //$NON-NLS-1$ private static LRUCache typeCache = new LRUCache(); private static List<String> builtinTypes = new ArrayList<String>(Arrays.asList("array", "static", "self", "parent")); public static List<IEvaluatedType> collectUseStatements(List<IType> types, boolean includeAbstract) { List<IEvaluatedType> statements = new ArrayList<IEvaluatedType>(); try { for (IType type : types) { IType currentNamespace = PHPModelUtils.getCurrentNamespace(type); boolean isInterface = PHPFlags.isInterface(type.getFlags()); IEvaluatedType evaluated = getEvaluatedType(type.getElementName(), currentNamespace); if (!statements.contains(evaluated)) statements.add(evaluated); for (IMethod method : type.getMethods()) { boolean isAbstract = PHPFlags.isAbstract(method.getFlags()); if (!isInterface && isAbstract && includeAbstract) { statements.addAll(collectParameterTypes(method)); } else if (isInterface) { statements.addAll(collectParameterTypes(method)); } } } } catch (ModelException e) { e.printStackTrace(); } return statements; } /** * Extension for {@link PHPModelUtils#getSuperTypeHierarchyMethod(IType, String, boolean, IProgressMonitor)} * * This method looking for non hierarchy also * * @since 0.18 */ public static IMethod[] getSuperTypeHierarchyMethod(IType type, String prefix, boolean exactName, IProgressMonitor monitor) throws CoreException { List<IMethod> list = Arrays.asList(PHPModelUtils.getSuperTypeHierarchyMethod(type, prefix, exactName, monitor)); superTypeHierarchyMethods(list, type, prefix, exactName); return list.toArray(new IMethod[list.size()]); } private static boolean superTypeHierarchyMethods(List<IMethod> list, IType type, String prefix, boolean exactName) { try { if ( type.getSuperClasses() == null || type.getSuperClasses().length == 0) { return false; } for (String n : type.getSuperClasses()) { if (type.getSourceModule().getType(n) != null) { final IType sub = type.getSourceModule().getType(n); for (IMethod m : sub.getMethods()) { if (!list.contains(m) && m.getElementName().equals(prefix) || (!exactName && m.getElementName().startsWith(prefix))) { list.add(m); return true; } } if (superTypeHierarchyMethods(list, sub, prefix, exactName)) { return true; } } } } catch (ModelException e) { } return false; } public static List<IEvaluatedType> collectParameterTypes(IMethod method) throws ModelException { List<IEvaluatedType> evaluated = new ArrayList<IEvaluatedType>(); IScriptProject project = method.getScriptProject(); if (project == null) return evaluated; IType currentNamespace = PHPModelUtils.getCurrentNamespace(method); String[] typeNames = null; if (method instanceof IPHPDocAwareElement) { typeNames = ((IPHPDocAwareElement) method).getReturnTypes(); } else { List<String> returnTypeList = new LinkedList<String>(); PHPDocBlock docBlock = PHPModelUtils.getDocBlock(method); if (docBlock == null) { return null; } PHPDocTag[] tags = docBlock.getTags(PHPDocTag.TagKind.PARAM); if (tags != null && tags.length > 0) { for (PHPDocTag phpDocTag : tags) { if (phpDocTag.getTypeReferences() != null && phpDocTag.getTypeReferences().size() > 0) { for (SimpleReference ref : phpDocTag .getTypeReferences()) { String type = ref.getName(); if (type != null && isValidType(type, project)) { returnTypeList.add(type); } } } } } typeNames = returnTypeList.toArray(new String[returnTypeList .size()]); } if (typeNames != null) { for (String typeName : typeNames) { Matcher m = ARRAY_TYPE_PATTERN.matcher(typeName); if (m.find()) { int offset = 0; try { offset = method.getSourceRange().getOffset(); } catch (ModelException e) { } IEvaluatedType t = getArrayType(m.group(), currentNamespace, offset); String name = t.getTypeName(); if (!evaluated.contains(name) && name.startsWith("$")) evaluated.add(t); } else { if (currentNamespace != null) { PHPDocBlock docBlock = PHPModelUtils .getDocBlock(method); ModuleDeclaration moduleDeclaration = SourceParserUtil .getModuleDeclaration(currentNamespace .getSourceModule()); if (typeName .indexOf(NamespaceReference.NAMESPACE_SEPARATOR) > 0) { // check if the first part // is an // alias,then get the full // name String prefix = typeName .substring( 0, typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR)); final Map<String, UsePart> result = PHPModelUtils .getAliasToNSMap(prefix, moduleDeclaration, docBlock.sourceStart(), currentNamespace, true); if (result.containsKey(prefix)) { String fullName = result.get(prefix) .getNamespace() .getFullyQualifiedName(); typeName = typeName.replace(prefix, fullName); } } else if (typeName .indexOf(NamespaceReference.NAMESPACE_SEPARATOR) < 0) { String prefix = typeName; final Map<String, UsePart> result = PHPModelUtils .getAliasToNSMap(prefix, moduleDeclaration, docBlock.sourceStart(), currentNamespace, true); if (result.containsKey(prefix)) { String fullName = result.get(prefix) .getNamespace() .getFullyQualifiedName(); typeName = fullName; } } } IEvaluatedType type = getEvaluatedType(typeName, currentNamespace); if (type != null && isValidType(typeName, project) && !evaluated.contains(type.getTypeName()) && !type.getTypeName().startsWith("$")) { evaluated.add(type); } } } } return evaluated; } public static IEvaluatedType getEvaluatedType(String typeName, IType currentNamespace) { IEvaluatedType type = PHPSimpleTypes.fromString(typeName); if (type == null) { if (typeName.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) != -1 || currentNamespace == null) { type = new PHPClassType(typeName); } else if (currentNamespace != null) { type = new PHPClassType(currentNamespace.getElementName(), typeName); } } return type; } public static MultiTypeType getArrayType(String type, IType currentNamespace, int offset) { int beginIndex = type.indexOf("[") + 1; int endIndex = type.lastIndexOf("]"); 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(""); } String[] typeNames = type.split(","); for (String name : typeNames) { if (!"".equals(name)) { if (name.indexOf(NamespaceReference.NAMESPACE_SEPARATOR) > 0 && currentNamespace != null) { // check if the first part is an // alias,then get the full name ModuleDeclaration moduleDeclaration = SourceParserUtil .getModuleDeclaration(currentNamespace .getSourceModule()); String prefix = name.substring(0, name .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(); name = name.replace(prefix, fullName); } } arrayType.addType(getEvaluatedType(name, currentNamespace)); } } return arrayType; } public static String getMethodSignature(MethodDeclaration method, IScriptProject project) { String signature = method.getName().toLowerCase(); return signature; // Integer num = new Integer(method.getArguments().size()); // // if (signature.equals("current")) { // System.err.println("c: " + num); // } // return signature + num.toString(); // for (Object o: method.getArguments()) { // // try { // FormalParameter param = (FormalParameter) o; // // SimpleReference type = param.getParameterType(); // if (type != null && isValidType(type.getName(), project)) { // // if (signature.startsWith("setmetadatafor")) { // System.err.println(": " + param.getParameterType().getName().toLowerCase()); // } // // signature += param.getParameterType().getName().toLowerCase(); // } // // } catch (ClassCastException e) { // // } // } // // return signature; } public static String getMethodSignature(IMethod method) { try { String methodSignature = method.getElementName().toLowerCase(); return methodSignature; // Integer num = new Integer(method.getParameters().length); // // if (methodSignature.equals("current")) { // System.err.println(num); // } // // return methodSignature + num; } catch (Exception e) { return ""; } // try { // for (IParameter param: method.getParameters()) { // try { // // if (isValidType(param.getType(), method.getScriptProject())) { // if (methodSignature.startsWith("setmetadatafor")) { // System.err.println(param.getType()); // } // methodSignature += param.getType().toLowerCase(); // } // // } catch (ClassCastException e) { // // } // } // } catch (ModelException e) { // e.printStackTrace(); // } // // return methodSignature; } public static boolean isBuiltinType(String type) { return builtinTypes.contains(type); } public static boolean isValidType(String type, IScriptProject project) { if (builtinTypes.contains(type)) { return true; } String key = type + project.getElementName(); if (typeCache.get(key) != null) return (Boolean) typeCache.get(key); if (type == null || "object".equals(type)) return (Boolean) typeCache.put(key, new Boolean(false)); IDLTKSearchScope scope = SearchEngine.createSearchScope(project); IType[] types = PHPModelAccess.getDefault().findTypes(type, MatchRule.EXACT, 0, 0, scope, new NullProgressMonitor()); return (Boolean) typeCache.put(key, new Boolean(types.length > 0)); } /** * Get full list of imported trait methods * * Entry<ImportedName, IMethod_from_trait> * * @param type * @return */ public static Map<String, IMethod> getImportedMethods(IType type) { Map<String, IMethod> ret = new HashMap<String, IMethod>(); UseTrait parsed = TraitUtils.parse(type); IDLTKSearchScope scope = TraitUtils.createSearchScope(type); Map<String, IType> traits = new HashMap<String, IType>(); Set<String> usedMethods = new HashSet<String>(); for (String traitName : parsed.getTraits()) { if (findType(type.getSourceModule(), traitName) != null) { traits.put(traitName, findType(type.getSourceModule(), traitName)); // out of index continue; } IType[] traitTypes = PHPModelAccess.getDefault().findTraits(traitName, MatchRule.EXACT, 0, 0, scope, new NullProgressMonitor()); if (traitTypes.length != 1) { continue; //more than one ignore it } traits.put(traitName, traitTypes[0]); } if (traits.size() == 0) { return ret; } //load aliases for (TraitAliasObject alias : parsed.getTraitAliases()) { IType trait = traits.get(alias.traitName); if (trait == null) { continue; } IMethod[] methods; try { methods = PHPModelUtils.getTypeMethod(trait, alias.traitMethodName, true); if (methods.length != 1) { continue; } usedMethods.add(alias.traitName + "::" + alias.traitMethodName); ret.put(alias.newMethodName, methods[0]); } catch (ModelException e) { Logger.logException(e); } } //load precedences for (Entry<String, TraitPrecedenceObject> entry : parsed.getPrecedenceMap().entrySet()) { IType trait = traits.get(entry.getValue().traitName); if (trait == null) { continue; } IMethod[] methods; try { methods = PHPModelUtils.getTypeMethod(trait, entry.getValue().traitMethodName, true); if (methods.length != 1) { continue; } usedMethods.add(entry.getValue().traitName + "::" + entry.getValue().traitMethodName); ret.put(entry.getValue().traitMethodName, methods[0]); } catch (ModelException e) { Logger.logException(e); } } // load other methods for (Entry<String, IType> entry : traits.entrySet()) { try { IMethod[] methods = PHPModelUtils.getTypeMethod(entry.getValue(), "", false); for (IMethod method : methods) { if (!ret.containsKey(method.getElementName()) && !usedMethods.contains(entry.getKey() + "::" + method.getElementName())) { usedMethods.add(entry.getKey() + "::" + method.getElementName()); ret.put(method.getElementName(), method); continue; } } } catch (ModelException e) { Logger.logException(e); } } return ret; } /** * @since 0.17.0 */ public static IModelElement getSourceElement(IModelElement element, int offset, int length) throws CoreException { Assert.isNotNull(element); ISourceModule sourceModule = (ISourceModule) element.getAncestor(IModelElement.SOURCE_MODULE); if (sourceModule == null) return null; return getSourceElement(sourceModule, offset, length); } /** * @since 0.17.0 */ public static IModelElement getSourceElement(ISourceModule sourceModule, int offset, int length) throws CoreException { Assert.isNotNull(sourceModule); IModelElement[] sourceElements = sourceModule.codeSelect(offset, length); if (sourceElements.length > 0) { if (sourceElements[0].getElementType() == IModelElement.METHOD && ((IMethod) sourceElements[0]).isConstructor()) { IType sourceType = fixInvalidSourceElement((IMethod) sourceElements[0], offset, length); if (sourceType != null) { return sourceType; } } return sourceElements[0]; } else { return null; } } /** * @since 0.17.0 */ private static IType fixInvalidSourceElement(IMethod sourceElement, int offset, int length) throws CoreException { ModuleDeclaration moduleDeclaration = SourceParserUtil.getModuleDeclaration(sourceElement.getSourceModule()); if (moduleDeclaration != null) { SourceTypeFinder sourceTypeFinder = new PDTModelUtils().new SourceTypeFinder(sourceElement.getSourceModule(), offset, length); try { moduleDeclaration.traverse(sourceTypeFinder); } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, PEXCorePlugin.PLUGIN_ID, e.getMessage(), e)); } return sourceTypeFinder.getSourceType(); } return null; } /** * @since 0.17.0 */ private class SourceTypeFinder extends PHPASTVisitor { private ISourceModule sourceModule; private int offset; private int length; private IType sourceType; public SourceTypeFinder(ISourceModule sourceModule, int offset, int length) { this.sourceModule = sourceModule; this.offset = offset; this.length = length; } @Override public boolean visit(FullyQualifiedReference s) throws Exception { if (s.sourceStart() <= offset && s.sourceEnd() >= offset + length) { IType[] sourceTypes = PDTTypeInferenceUtils.getTypes(s, sourceModule); if (sourceTypes.length > 0) { sourceType = sourceTypes[0]; return false; } } return true; } public IType getSourceType() { return sourceType; } } /** * Allow to find IType in current module (source file) * * @since 0.18 */ public static IType findType(ISourceModule module, String fqn) { try { for (IType t : module.getAllTypes()) { if (PHPModelUtils.getFullName(t).equals(fqn)) { return t; } } } catch (ModelException e) { e.printStackTrace(); } return null; } /** * Ease method to select to find IType (Classes, Interfaces, Traits) by FQN * * @since 0.18 */ public static IType[] findTypes(IScriptProject project, String fqn) { Set<IType> list = new HashSet<IType>(); IDLTKSearchScope searchScope = SearchEngine.createSearchScope(project); IType[] types = PHPModelAccess.getDefault().findTypes(fqn, MatchRule.EXACT, 0, 0, searchScope, new NullProgressMonitor()); for (IType type : types) { if (fqn.equals(PHPModelUtils.getFullName(type))) { list.add(type); } } types = PHPModelAccess.getDefault().findTraits(fqn, MatchRule.EXACT, 0, 0, searchScope, new NullProgressMonitor()); for (IType type : types) { if (fqn.equals(PHPModelUtils.getFullName(type))) { list.add(type); } } return list.toArray(new IType[list.size()]); } /** * Too slow on startup * Because sometimes DLTK index is not ready (for ex. while start), force options allow to search file by file. * * @since 0.18 */ public static IType[] findTypes(IScriptProject project, String fqn, boolean force) throws ModelException { if (!force) { return findTypes(project, fqn); } Set<IType> list = new HashSet<IType>(); for (IProjectFragment f : project.getAllProjectFragments()) { rawSearch(list, f, fqn); } return list.toArray(new IType[list.size()]); } private static void rawSearch(Set<IType> list, IModelElement el, String name) throws ModelException { if (el instanceof IScriptFolder ) { for (IModelElement sub : ((IScriptFolder) el).getChildren()) { rawSearch(list, sub, name); } } else if (el instanceof IProjectFragment ) { for (IModelElement sub : ((IProjectFragment) el).getChildren()) { rawSearch(list, sub, name); } } else if (el instanceof ISourceModule){ ISourceModule mod = (ISourceModule) el; for (IType t : mod.getAllTypes()) { if (PHPModelUtils.getFullName(t).equals(name)) { list.add(t); } } } } /** * @since 0.22.0 */ public static boolean inResourceWithSameName(IResource resource, String typeName) throws CoreException { if (resource instanceof IFile) { return resource.getName().substring(0, resource.getName().indexOf(resource.getFileExtension()) - 1).equals(typeName); } else if (resource instanceof IFolder) { return resource.getName().equals(typeName.substring(typeName.lastIndexOf(BACK_SLASH) + 1)); } else { throw new CoreException(new Status(IStatus.ERROR, PEXCorePlugin.PLUGIN_ID, "The resource is neither a file or folder")); //$NON-NLS-1$ } } }