/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.model.utils; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.aptana.core.logging.IdeLog; import com.aptana.core.util.CollectionsUtil; import com.aptana.editor.php.PHPEditorPlugin; import com.aptana.editor.php.indexer.EntryUtils; import com.aptana.editor.php.indexer.IElementEntry; import com.aptana.editor.php.indexer.IElementsIndex; import com.aptana.editor.php.indexer.IPHPIndexConstants; import com.aptana.editor.php.internal.contentAssist.ContentAssistCollectors; import com.aptana.editor.php.internal.contentAssist.PHPContentAssistProcessor; import com.aptana.editor.php.internal.core.builder.IBuildPath; import com.aptana.editor.php.internal.core.builder.IModule; import com.aptana.editor.php.internal.indexer.AbstractPHPEntryValue; import com.aptana.editor.php.internal.indexer.ClassPHPEntryValue; import com.aptana.editor.php.internal.indexer.ElementsIndexingUtils; import com.aptana.editor.php.internal.indexer.language.PHPBuiltins; /** * Type hierarchy utilities * * @author Denis Denisenko */ public final class TypeHierarchyUtils { private static final int MAX_ANCESTORS_LIMIT = 20; /** * Finds all class ancestors. * * @param module * - module, class is defined in. * @param className * - class name. * @param namespace * @param aliases * @return list of ancestor entries. */ public static List<IElementEntry> getClassAncestors(IModule module, String className, IElementsIndex index, String namespace, Map<String, String> aliases) { List<IElementEntry> result = new ArrayList<IElementEntry>(); List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, className); for (IElementEntry classEntry : classEntries) { if (module == null || classEntry.getModule().equals(module)) { findClassAncestorsRecursivelly(classEntry, result, index, namespace, aliases); } } return result; } /** * Finds direct class ancestors. * * @param module * - module, class is defined in. * @param className * - class name. * @return list of ancestor entries. */ public static List<IElementEntry> getDirectClassAncestors(IModule module, String className, IElementsIndex index) { List<IElementEntry> result = new ArrayList<IElementEntry>(); List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, className); for (IElementEntry classEntry : classEntries) { if (module == null || classEntry.getModule().equals(module)) { findClassAncestors(classEntry, result, index); } } return result; } /** * Gets direct class descendants (both classes and interfaces). * * @param module * - module. * @param className * - class name. * @param index * - index. * @return direct class descendants */ public static List<IElementEntry> getDirectClassDescendants(IModule module, String className, IElementsIndex index) { List<IElementEntry> result = new ArrayList<IElementEntry>(); List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, className); for (IElementEntry classEntry : classEntries) { if (module == null || classEntry.getModule().equals(module)) { findClassDescendants(classEntry, result, index); } } return result; } /** * Gets all class descendants (both classes and interfaces). * * @param module * - module. * @param className * - class name. * @param index * - index. * @param namespace * @param aliases * @return direct class descendants */ public static List<IElementEntry> getClassDescendants(IModule module, String className, IElementsIndex index, String namespace, Map<String, String> aliases) { List<IElementEntry> result = new ArrayList<IElementEntry>(); List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, className); for (IElementEntry classEntry : classEntries) { if (module == null || classEntry.getModule().equals(module)) { findClassDescendantsRecursivelly(classEntry, result, index, namespace, aliases); } } return result; } /** * Adds all ancestors for the type set. * * @param types * - types, which ancestors to add. * @param index * - index to use. * @param namespace * A namespace to accept * @param aliases * The aliases ('use' expressions in the namespace) * @return types with ancestors added. */ public static Set<String> addAllAncestors(Set<String> types, IElementsIndex index, String namespace, Map<String, String> aliases) { // FIXME: Shalom - This should probably get a set of namespaces to accept. // Or better, the getClassAncestors() should be the one that deals with it. Set<String> result = new LinkedHashSet<String>(); for (String type : types) { result.add(type); // Collect the entries from the ancestors. The collection will hold valid namespace types which takes into // account any aliased types we have in the code. List<IElementEntry> entries = getClassAncestors(null, type, index, namespace, aliases); for (IElementEntry entry : entries) { result.add(ElementsIndexingUtils.getFirstNameInPath(entry.getEntryPath())); } } return result; } /** * Finds all classes with the name specified in the module specified. * * @param module * - module, class is defined in. * @param className * - class name. * @return list of class entries. * @param index * - index to use. */ public static List<IElementEntry> getClasses(IModule module, String className, IElementsIndex index) { List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, className); return classEntries; } /** * Filters entries by build-path. Those entries that are not reachable by module's build-path are filtered out. * * @param module * - module. * @param toFilter * - entries to filter. * @return filtered entries. */ public static List<IElementEntry> filterByBuildPath(IModule module, List<IElementEntry> toFilter) { IBuildPath buildPath = module.getBuildPath(); return filterByBuildPath(buildPath, toFilter); } /** * Filters entries by build-path. Those entries that are not reachable by module's build-path are filtered out. * * @param module * - module. * @param entry * - entry to check. * @return true if on build-path, false otherwise. */ public static boolean isOnBuildPath(IModule module, IElementEntry entry) { IBuildPath buildPath = module.getBuildPath(); return isOnBuildPath(buildPath, entry); } /** * Filters entries by build-path. Those entries that are not reachable by module's build-path are filtered out. * * @param buildPath * - build path. * @param entry * - entry to check. * @return true if on build-path, false otherwise */ public static boolean isOnBuildPath(IBuildPath buildPath, IElementEntry entry) { IBuildPath currentEntryBuildPath = entry.getModule().getBuildPath(); Set<IBuildPath> dependencies = buildPath.getDependencies(); return buildPath.equals(currentEntryBuildPath) || dependencies.contains(entry.getModule().getBuildPath()); } /** * Filters entries by build-path. Those entries that are not reachable by module's build-path are filtered out. * * @param buildPath * - build path. * @param toFilter * - entries to filter. * @return filtered entries. */ public static List<IElementEntry> filterByBuildPath(IBuildPath buildPath, List<IElementEntry> toFilter) { List<IElementEntry> result = new ArrayList<IElementEntry>(); Set<IBuildPath> dependencies = (buildPath != null) ? buildPath.getDependencies() : new HashSet<IBuildPath>(1); for (IElementEntry entry : toFilter) { IModule module = entry.getModule(); if (module == null) { if (PHPBuiltins.getInstance().isBuiltinClassOrConstant(entry.getEntryPath())) { result.add(entry); } } else { IBuildPath currentEntryBuildPath = module.getBuildPath(); if (buildPath.equals(currentEntryBuildPath) || dependencies.contains(currentEntryBuildPath)) { result.add(entry); } } } return result; } /** * Check if a given entry is in the namespace specified. * * @param value * @param namespace * @return True, in case the value's namespace matches the given namespace . */ public static boolean isInNamespace(AbstractPHPEntryValue value, String namespace) { if (value == null) { return false; } String valueNamespace = value.getNameSpace(); if (valueNamespace == null || valueNamespace.length() == 0) { return namespace == null || namespace.length() == 0 || PHPContentAssistProcessor.GLOBAL_NAMESPACE.equals(namespace); } else { return valueNamespace.equals(namespace); } } /** * Check if a given entry is in one of the namespaces specified. * * @param value * @param namespaces * A set of namespaces * @return True, in case the value's namespace matches one of given namespace . */ public static boolean isInNamespace(AbstractPHPEntryValue value, Set<String> namespaces) { if (value == null) { return false; } String valueNamespace = value.getNameSpace(); if (valueNamespace == null || valueNamespace.length() == 0) { return namespaces == null || namespaces.isEmpty() || namespaces.contains(PHPContentAssistProcessor.GLOBAL_NAMESPACE); } else { return namespaces != null && namespaces.contains(valueNamespace); } } /** * Check if a given entry is in one of the aliases specified. * * @param entryPath * @param aliases * @return True, in case the entry is included in the aliases */ public static boolean isInAliases(String entryPath, Map<String, String> aliases) { if (entryPath == null) { return false; } return aliases.containsKey(entryPath) || aliases.containsValue(entryPath); } /** * Finds class ancestors recursively. * * @param _module * - module, class * @param classEntry * - class entry. * @param toFill * - list of ancestors to fill. * @param index * - index to use. * @param namespace * @param aliases * - A map of aliases ('use' expressions) - Can be null. Note that this map will only be used in the * first cycle of the lookup recursion, and will be used to replace any alias name use with the real * class/interface name. */ private static void findClassAncestorsRecursivelly(IElementEntry classEntry, List<IElementEntry> toFill, IElementsIndex index, String namespace, Map<String, String> aliases) { // Limit the ancestors list. At some point this would just be an indication that we got into an // infinite recursion. See https://aptana.lighthouseapp.com/projects/35272/tickets/2158 // A simple cause for this issue may be two classes that extend each other. if (toFill.size() > MAX_ANCESTORS_LIMIT) { IdeLog.logWarning( PHPEditorPlugin.getDefault(), "Max ancestors reached. Hierarchy lookup stopped. Please check your class hierarchy", PHPEditorPlugin.DEBUG_SCOPE); //$NON-NLS-1$ return; } // FIXME - Shalom: This lookup should take into consideration the namespaces and the aliases that are involved // in the hierarchy Object value = classEntry.getValue(); if (!(value instanceof ClassPHPEntryValue)) { return; } ClassPHPEntryValue entryValue = (ClassPHPEntryValue) value; String superClassName = entryValue.getSuperClassname(); IBuildPath classEntryBuildPath = null; if (classEntry.getModule() != null) { classEntryBuildPath = classEntry.getModule().getBuildPath(); } if (superClassName != null) { boolean aliasReplacement = false; if (aliases != null && aliases.containsKey(superClassName)) { // Replace the alias with the real type superClassName = aliases.get(superClassName); aliasReplacement = true; } if (superClassName.startsWith(PHPContentAssistProcessor.GLOBAL_NAMESPACE)) { superClassName = superClassName.substring(1); } List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, superClassName); if (classEntries.isEmpty()) { Set<String> superClass = new HashSet<String>(1); superClass.add(superClassName); Set<IElementEntry> entries = ContentAssistCollectors.collectBuiltinTypeEntries(superClass, true); classEntries.addAll(entries); } // Collect the ancestors. The collectAncestors method will make recursive calls to this one in order to // keep on collecting up the hierarchy. collectAncestors(toFill, index, namespace, classEntryBuildPath, aliasReplacement, classEntries); } List<String> interfaces = entryValue.getInterfaces(); if (!CollectionsUtil.isEmpty(interfaces)) { for (String interfaceName : interfaces) { boolean aliasReplacement = false; if (aliases != null && aliases.containsKey(interfaceName)) { // Replace the alias with the real type interfaceName = aliases.get(interfaceName); aliasReplacement = true; } if (interfaceName.startsWith(PHPContentAssistProcessor.GLOBAL_NAMESPACE)) { interfaceName = interfaceName.substring(1); } List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, interfaceName); // Collect the ancestors. The collectAncestors method will make recursive calls to this one in order to // keep on collecting up the hierarchy. collectAncestors(toFill, index, namespace, classEntryBuildPath, aliasReplacement, classEntries); } } // Add the traits List<String> traits = entryValue.getTraits(); if (!CollectionsUtil.isEmpty(traits)) { for (String trait : traits) { if (trait.startsWith(PHPContentAssistProcessor.GLOBAL_NAMESPACE)) { trait = trait.substring(1); } List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, trait); // Collect the ancestors. The collectAncestors method will make recursive calls to this one in order to // keep on collecting up the hierarchy. collectAncestors(toFill, index, namespace, classEntryBuildPath, false, classEntries); } } } /** * Collect the class or interface ancestors. * * @param toFill * @param index * @param namespace * @param classEntryBuildPath * @param aliasReplacement * Indicate that the ancestor name was replaced with an alias name. * @param entries */ private static void collectAncestors(List<IElementEntry> toFill, IElementsIndex index, String namespace, IBuildPath classEntryBuildPath, boolean aliasReplacement, List<IElementEntry> entries) { List<IElementEntry> classEntriesOnBuildPath = filterByBuildPath(classEntryBuildPath, entries); // Add to the result in case the entry is in the namespace, or in case it is not, and it has an alias // mapping. for (IElementEntry entry : classEntriesOnBuildPath) { Object eValue = entry.getValue(); if (eValue instanceof AbstractPHPEntryValue) { if (aliasReplacement || isInNamespace((AbstractPHPEntryValue) eValue, namespace)) { toFill.add(entry); // Make a recursive call. This time with null aliases, since we only look into alias replacement // in the first level of computation. findClassAncestorsRecursivelly(entry, toFill, index, namespace, null); } } } } /** * Finds class direct ancestors. * * @param _module * - module, class * @param classEntry * - class entry. * @param toFill * - list of ancestors to fill. * @param index * - index to use. */ private static void findClassAncestors(IElementEntry classEntry, List<IElementEntry> toFill, IElementsIndex index) { Object value = classEntry.getValue(); if (!(value instanceof ClassPHPEntryValue)) { return; } ClassPHPEntryValue entryValue = (ClassPHPEntryValue) value; IBuildPath classEntryBuildPath = classEntry.getModule().getBuildPath(); String superClassName = entryValue.getSuperClassname(); if (superClassName != null) { if (superClassName.startsWith(PHPContentAssistProcessor.GLOBAL_NAMESPACE)) { superClassName = superClassName.substring(1); } List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, superClassName); List<IElementEntry> classEntriesOnBuildPath = filterByBuildPath(classEntryBuildPath, classEntries); toFill.addAll(classEntriesOnBuildPath); } List<String> interfaces = entryValue.getInterfaces(); if (interfaces != null && !interfaces.isEmpty()) { for (String className : interfaces) { if (className.startsWith(PHPContentAssistProcessor.GLOBAL_NAMESPACE)) { className = className.substring(1); } List<IElementEntry> classEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, className); List<IElementEntry> classEntriesOnBuildPath = filterByBuildPath(classEntryBuildPath, classEntries); toFill.addAll(classEntriesOnBuildPath); } } } /** * Finds class descendants. * * @param classEntry * - class entry. * @param result * - result to add to. * @param index * - index to use. */ private static void findClassDescendants(IElementEntry classEntry, List<IElementEntry> result, IElementsIndex index) { List<IElementEntry> typeEntries = index.getEntriesStartingWith(IPHPIndexConstants.CLASS_CATEGORY, ""); //$NON-NLS-1$ IBuildPath classEntryBuildPath = classEntry.getModule().getBuildPath(); String typeName = ElementsIndexingUtils.getLastNameInPath(classEntry.getEntryPath()); if (EntryUtils.isInterface(classEntry)) { // searching interface descendants for (IElementEntry typeEntry : typeEntries) { ClassPHPEntryValue value = (ClassPHPEntryValue) typeEntry.getValue(); List<String> interfaceNames = value.getInterfaces(); if (interfaceNames != null && interfaceNames.size() != 0) { for (String interfaceName : interfaceNames) { if (typeName.equals(interfaceName) && isOnBuildPath(classEntryBuildPath, typeEntry)) { result.add(typeEntry); } } } } } else { // searching class descendants for (IElementEntry typeEntry : typeEntries) { ClassPHPEntryValue value = (ClassPHPEntryValue) typeEntry.getValue(); String currentSuperClassName = value.getSuperClassname(); if (currentSuperClassName != null) { if (typeName.equals(currentSuperClassName) && isOnBuildPath(classEntryBuildPath, typeEntry)) { result.add(typeEntry); } } } } } /** * Finds class descendants. * * @param classEntry * - class entry. * @param result * - result to add to. * @param index * - index to use. * @param namespace * @param aliases */ private static void findClassDescendantsRecursivelly(IElementEntry classEntry, List<IElementEntry> result, IElementsIndex index, String namespace, Map<String, String> aliases) { List<IElementEntry> typeEntries = index.getEntriesStartingWith(IPHPIndexConstants.CLASS_CATEGORY, ""); //$NON-NLS-1$ IBuildPath classEntryBuildPath = classEntry.getModule().getBuildPath(); String typeName = ElementsIndexingUtils.getLastNameInPath(classEntry.getEntryPath()); if (EntryUtils.isInterface(classEntry)) { // searching interface descendants for (IElementEntry typeEntry : typeEntries) { ClassPHPEntryValue value = (ClassPHPEntryValue) typeEntry.getValue(); List<String> interfaceNames = value.getInterfaces(); if (interfaceNames != null && interfaceNames.size() != 0) { for (String interfaceName : interfaceNames) { if (typeName.equals(interfaceName) && isOnBuildPath(classEntryBuildPath, typeEntry)) { result.add(typeEntry); findClassAncestorsRecursivelly(typeEntry, result, index, namespace, aliases); } } } } } else { // searching class descendants for (IElementEntry typeEntry : typeEntries) { ClassPHPEntryValue value = (ClassPHPEntryValue) typeEntry.getValue(); String currentSuperClassName = value.getSuperClassname(); if (currentSuperClassName != null) { if (typeName.equals(currentSuperClassName) && isOnBuildPath(classEntryBuildPath, typeEntry)) { result.add(typeEntry); findClassAncestorsRecursivelly(typeEntry, result, index, namespace, aliases); } } } } } /** * TypeHierarchyUtils private constructor. */ private TypeHierarchyUtils() { } }