package com.aptana.editor.php.internal.contentAssist; import static com.aptana.editor.php.internal.contentAssist.PHPContentAssistProcessor.DOLLAR_SIGN; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.aptana.core.util.StringUtil; 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.core.builder.IModule; import com.aptana.editor.php.internal.indexer.ClassPHPEntryValue; import com.aptana.editor.php.internal.indexer.FunctionPHPEntryValue; import com.aptana.editor.php.internal.indexer.VariablePHPEntryValue; import com.aptana.editor.php.internal.parser.nodes.PHPClassParseNode; import com.aptana.editor.php.internal.parser.nodes.PHPConstantNode; import com.aptana.editor.php.internal.parser.nodes.PHPFunctionParseNode; import com.aptana.editor.php.internal.parser.nodes.PHPVariableParseNode; import com.aptana.editor.php.internal.parser.nodes.Parameter; import com.aptana.parsing.ast.IParseNode; /** * A class that contains utility functions that are used when collecting proposals in the PHP content assist processor. * * @author Shalom Gibly <sgibly@aptana.com> */ public class ContentAssistCollectors { /** * Collects entries for the variable. * * @param index * - index to use. * @param varName * - variable name (including $ sign). * @param types * - types to collect variables from. * @param exactMatch * - whether to check for exact match of the variable name. * @param aliases * @param namespace * @param filter * - filter to apply. * @return types */ public static Set<IElementEntry> collectVariableEntries(IElementsIndex index, String varName, Set<String> types, boolean exactMatch, Map<String, String> aliases, final String namespace) { Set<IElementEntry> result = new LinkedHashSet<IElementEntry>(); // searching for variables String rightVarName = varName.substring(1); for (String leftType : types) { String typeName = processTypeName(aliases, namespace, leftType); String entryPath = typeName + rightVarName; List<IElementEntry> currentEntries = null; if (exactMatch) { currentEntries = index.getEntries(IPHPIndexConstants.VAR_CATEGORY, entryPath); } else { currentEntries = index.getEntriesStartingWith(IPHPIndexConstants.VAR_CATEGORY, entryPath); } List<?> items = ContentAssistUtils.selectModelElements(leftType, true); if (items != null && !items.isEmpty()) { String lowCaseFuncName = (varName != null) ? varName.toLowerCase() : StringUtil.EMPTY; for (Object obj : items) { if (obj instanceof PHPClassParseNode) { final PHPClassParseNode classParseNode = (PHPClassParseNode) obj; IParseNode[] children = classParseNode.getChildren(); for (IParseNode child : children) { if (child instanceof PHPVariableParseNode && !(child instanceof PHPConstantNode)) { final PHPVariableParseNode varParseNode = (PHPVariableParseNode) child; if (!varParseNode.isField()) { continue; } String funcNodeName = varParseNode.getNodeName(); if (funcNodeName != null) { if (!funcNodeName.startsWith(DOLLAR_SIGN)) { funcNodeName = DOLLAR_SIGN + funcNodeName; // $codepro.audit.disable } String lowCaseFuncNodeName = funcNodeName.toLowerCase(); if (exactMatch) { if (!lowCaseFuncName.equals(lowCaseFuncNodeName)) continue; } else { if (!lowCaseFuncNodeName.startsWith(lowCaseFuncName)) continue; } } // Add an element entry result.add(new IElementEntry() { private VariablePHPEntryValue value; public int getCategory() { return IPHPIndexConstants.VAR_CATEGORY; } public String getEntryPath() { // Returns the function name. // This is useful when we have a // built-in class function // completion. return classParseNode.getNodeName() + IElementsIndex.DELIMITER + varParseNode.getNodeName(); } public String getLowerCaseEntryPath() { String path = getEntryPath(); return (path != null) ? path.toLowerCase() : StringUtil.EMPTY; } public IModule getModule() { return null; } public Object getValue() { if (value != null) { return value; } // @formatter:off value = new VariablePHPEntryValue(varParseNode.getModifiers(), varParseNode.isParameter(), varParseNode.isLocalVariable(), varParseNode.isField(), varParseNode.getNodeType(), varParseNode.getStartingOffset(), namespace); // @formatter:on return value; } }); } } } } } if (currentEntries != null) { result.addAll(currentEntries); } } return result; } /** * Collects entries for the variable. * * @param index * - index to use. * @param constName * - variable name (including $ sign). * @param types * - types to collect variables from. * @param exactMatch * - whether to check for exact match of the variable name. * @param aliases * @param filter * - filter to apply. * @param namespace * - namespace lookup (can be null) * @return types */ public static Set<IElementEntry> collectConstEntries(IElementsIndex index, String constName, Set<String> types, boolean exactMatch, Map<String, String> aliases, final String namespace) { Set<IElementEntry> result = new LinkedHashSet<IElementEntry>(); // searching for variables String rightConstName = constName; for (String leftType : types) { String typeName = processTypeName(aliases, namespace, leftType); String entryPath = typeName + rightConstName; List<IElementEntry> currentEntries = null; if (exactMatch) { currentEntries = index.getEntries(IPHPIndexConstants.CONST_CATEGORY, entryPath); } else { currentEntries = index.getEntriesStartingWith(IPHPIndexConstants.CONST_CATEGORY, entryPath); } if (currentEntries == null || currentEntries.isEmpty()) { List<?> items = ContentAssistUtils.selectModelElements(leftType, true); if (items != null && !items.isEmpty()) { String lowCaseConstName = (constName != null) ? constName.toLowerCase() : StringUtil.EMPTY; for (Object obj : items) { if (obj instanceof PHPClassParseNode) { final PHPClassParseNode classParseNode = (PHPClassParseNode) obj; IParseNode[] children = classParseNode.getChildren(); for (IParseNode child : children) { if (child instanceof PHPConstantNode) { final PHPConstantNode constantNode = (PHPConstantNode) child; String constNodeName = constantNode.getNodeName(); if (constNodeName != null) { String lowCaseFuncNodeName = constNodeName.toLowerCase(); if (exactMatch) { if (!lowCaseConstName.equals(lowCaseFuncNodeName)) continue; } else { if (!lowCaseFuncNodeName.startsWith(lowCaseConstName)) continue; } } result.add(new IElementEntry() { private VariablePHPEntryValue value; public int getCategory() { return IPHPIndexConstants.CONST_CATEGORY; } public String getEntryPath() { return classParseNode.getNodeName() + IElementsIndex.DELIMITER + constantNode.getNodeName(); } public String getLowerCaseEntryPath() { String path = getEntryPath(); return (path != null) ? path.toLowerCase() : StringUtil.EMPTY; } public IModule getModule() { return null; } public Object getValue() { if (value != null) { return value; } // @formatter:off value = new VariablePHPEntryValue(constantNode.getModifiers(), constantNode.isParameter(), constantNode.isLocalVariable(), constantNode.isField(), constantNode.getNodeType(), constantNode.getStartingOffset(), namespace); // @formatter:on return value; } }); } } } } } } if (currentEntries != null) { result.addAll(currentEntries); } } return result; } /** * Prepare the type name with the namespace. * * @param aliases * @param namespace * The namespace (should be without the separaotr at the beginning) * @param typeName * The type that we want to append the namespace with * @return A processed type name with a namespace prefix, in case a valid namespace was passed. */ private static String processTypeName(Map<String, String> aliases, String namespace, String typeName) { String result = typeName; if (namespace != null) { if (typeName.startsWith(namespace + PHPContentAssistProcessor.GLOBAL_NAMESPACE)) { String type = typeName.substring(namespace.length() + 1); if (aliases.containsValue(type)) { result = type; } } else if (aliases.containsKey(typeName)) { result = aliases.get(typeName); } else if (!aliases.containsValue(typeName) && !typeName.contains(PHPContentAssistProcessor.GLOBAL_NAMESPACE) && !PHPContentAssistProcessor.GLOBAL_NAMESPACE.equals(namespace)) { // We need to look for the type name in the values of the aliases. // For each value, we compare the last segment to the type, and if they match, we treat that value as // the result. boolean found = false; Set<Entry<String, String>> entrySet = aliases.entrySet(); for (Entry<String, String> entry : entrySet) { String value = entry.getValue(); int lastNamesaceDelimiter = value.lastIndexOf(PHPContentAssistProcessor.GLOBAL_NAMESPACE); if (lastNamesaceDelimiter > -1 && lastNamesaceDelimiter < value.length() - 1) { if (typeName.equals(value.substring(lastNamesaceDelimiter + 1))) { result = value; found = true; break; } } } if (!found) { result = namespace + PHPContentAssistProcessor.GLOBAL_NAMESPACE + typeName; } } } return result + IElementsIndex.DELIMITER; } /** * Collects function entries. * * @param index * - index to use. * @param funcName * - function name. * @param types * - types to collect functions (methods) from. * @param exactMatch * - whether to check for exact match of the function name. * @param aliases * @param namespace * @return types */ public static Set<IElementEntry> collectFunctionEntries(IElementsIndex index, String funcName, Set<String> types, boolean exactMatch, Map<String, String> aliases, String namespace) { Set<IElementEntry> result = new LinkedHashSet<IElementEntry>(); // searching for methods String rightMethodName = funcName; for (String leftType : types) { String typeName = processTypeName(aliases, namespace, leftType); String entryPath = typeName + rightMethodName; List<IElementEntry> currentEntries = null; if (exactMatch) { currentEntries = index.getEntries(IPHPIndexConstants.FUNCTION_CATEGORY, entryPath); } else { currentEntries = index.getEntriesStartingWith(IPHPIndexConstants.FUNCTION_CATEGORY, entryPath); } if (currentEntries == null || currentEntries.isEmpty()) { // PHPGlobalIndexer.getInstance().getIndex().getEntries(-1, // leftType.toLowerCase()) List<?> items = ContentAssistUtils.selectModelElements(leftType, true); if (items != null && !items.isEmpty()) { String lowCaseFuncName = (funcName != null) ? funcName.toLowerCase() : StringUtil.EMPTY; for (Object obj : items) { if (obj instanceof PHPClassParseNode) { final PHPClassParseNode classParseNode = (PHPClassParseNode) obj; IParseNode[] children = classParseNode.getChildren(); for (IParseNode child : children) { if (child instanceof PHPFunctionParseNode) { final PHPFunctionParseNode functionParseNode = (PHPFunctionParseNode) child; String funcNodeName = functionParseNode.getNodeName(); if (funcNodeName != null) { String lowCaseFuncNodeName = funcNodeName.toLowerCase(); if (exactMatch) { if (!lowCaseFuncName.equals(lowCaseFuncNodeName)) continue; } else { if (!lowCaseFuncNodeName.startsWith(lowCaseFuncName)) continue; } } // Add an element entry for an element // that is probably located in the // phpfunctions5 // or outside the workspace. result.add(new IElementEntry() { private FunctionPHPEntryValue value; public int getCategory() { return IPHPIndexConstants.FUNCTION_CATEGORY; } public String getEntryPath() { // Returns the function name. // This is useful when we have a // built-in class function // completion. return classParseNode.getNodeName() + IElementsIndex.DELIMITER + functionParseNode.getNodeName(); } public String getLowerCaseEntryPath() { String path = getEntryPath(); return (path != null) ? path.toLowerCase() : StringUtil.EMPTY; } public IModule getModule() { return null; } public Object getValue() { if (value != null) { return value; } Parameter[] parameters = functionParseNode.getParameters(); Map<String, Set<Object>> parametersMap = null; boolean[] mandatories = null; ArrayList<Integer> startPositions = null; parametersMap = new LinkedHashMap<String, Set<Object>>(parameters.length); if (parameters != null) { mandatories = new boolean[parameters.length]; startPositions = new ArrayList<Integer>(parameters.length); for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; String nameIdentifier = parameter.getVariableName(); if (nameIdentifier == null) { continue; } if (nameIdentifier.startsWith(DOLLAR_SIGN)) { nameIdentifier = nameIdentifier.substring(1); } String parameterType = parameter.getClassType(); Set<Object> types = null; if (parameterType != null) { types = new HashSet<Object>(1); types.add(parameterType); } parametersMap.put(nameIdentifier, types); mandatories[i] = StringUtil.EMPTY.equals(parameter .getDefaultValue()); // Always set to that, since we have no other information here startPositions.add(functionParseNode.getStartingOffset()); } } int[] startPositionsArray = new int[startPositions.size()]; for (int p = 0; p < startPositions.size(); p++) { startPositionsArray[p] = startPositions.get(p); } value = new FunctionPHPEntryValue(functionParseNode.getModifiers(), true, parametersMap, startPositionsArray, mandatories, functionParseNode .getStartingOffset(), StringUtil.EMPTY); return value; } }); } } } } } } if (currentEntries != null) { result.addAll(currentEntries); } } return result; } /** * Collects function entries. * * @param types * - types to collect. * @param exactMatch * - whether to check for exact match of the function name. * @return A set of collected {@link IElementEntry}s with the requested types. */ public static Set<IElementEntry> collectBuiltinTypeEntries(Set<String> types, boolean exactMatch) { Set<IElementEntry> result = new LinkedHashSet<IElementEntry>(); for (String typeName : types) { List<?> items = ContentAssistUtils.selectModelElements(typeName, true); if (items != null && !items.isEmpty()) { String lowCaseClassName = (typeName != null) ? typeName.toLowerCase() : StringUtil.EMPTY; for (Object obj : items) { if (obj instanceof PHPClassParseNode) { final PHPClassParseNode classParseNode = (PHPClassParseNode) obj; String nodeName = classParseNode.getNodeName(); if (nodeName != null) { String lowCaseClassNodeName = nodeName.toLowerCase(); if (exactMatch) { if (!lowCaseClassName.equals(lowCaseClassNodeName)) continue; } else { if (!lowCaseClassNodeName.startsWith(lowCaseClassName)) continue; } // Add the builtin element result.add(new IElementEntry() { private ClassPHPEntryValue value; public int getCategory() { return IPHPIndexConstants.CLASS_CATEGORY; } public String getEntryPath() { // Returns the class name. return classParseNode.getNodeName(); } public String getLowerCaseEntryPath() { String path = getEntryPath(); return (path != null) ? path.toLowerCase() : StringUtil.EMPTY; } public IModule getModule() { return null; } public Object getValue() { if (value != null) { return value; } String superClassname = classParseNode.getSuperClassname(); List<String> interfaces = classParseNode.getInterfaces(); value = new ClassPHPEntryValue(classParseNode.getModifiers(), superClassname, interfaces, StringUtil.EMPTY); return value; } }); } } } } } return result; } }