package com.nvlad.yii2support.common; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.util.ArrayUtil; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocProperty; import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; import com.jetbrains.php.lang.psi.elements.*; import com.jetbrains.php.lang.psi.resolve.types.PhpType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * Created by NVlad on 11.01.2017. */ public class ClassUtils { public static int getParamIndex(Method method, String[] paramNames) { for (String name: paramNames) { int index = getParamIndex(method, name); if (index > -1) return index; } return -1; } public static int getParamIndex(Method method, String paramName) { for (int i = 0; i < method.getParameters().length; i++) { Parameter param = method.getParameters()[i]; if (param.getName().equals(paramName)) return i; } return -1; } @Nullable public static PhpClass getPhpClassUniversal(Project project, PhpPsiElement value) { if (value instanceof MethodReference && (value.getName().equals("className") || value.getName().equals("tableName"))) { MethodReference methodRef = (MethodReference) value; return getPhpClass(methodRef.getClassReference()); } if (value instanceof ClassConstantReference) { ClassConstantReference classRef = (ClassConstantReference) value; return getPhpClass(classRef); } if (value instanceof StringLiteralExpression) { StringLiteralExpression str = (StringLiteralExpression) value; PhpIndex phpIndex = PhpIndex.getInstance(project); PhpClass classRef = getClass(phpIndex, str.getContents()); return classRef; } return null; } @Nullable public static PhpClass getClass(PhpIndex phpIndex, String className) { Collection<PhpClass> classes = phpIndex.getAnyByFQN(className); return classes.isEmpty() ? null : classes.iterator().next(); } @Nullable public static PhpClass getPhpClass(PhpPsiElement phpPsiElement) { while (phpPsiElement != null) { if (phpPsiElement instanceof ClassConstantReference) { phpPsiElement = ((ClassConstantReference) phpPsiElement).getClassReference(); } if (phpPsiElement instanceof ClassReference) { return (PhpClass) ((ClassReference) phpPsiElement).resolve(); } if (phpPsiElement instanceof NewExpression) { ClassReference classReference = ((NewExpression) phpPsiElement).getClassReference(); if (classReference != null) { PhpPsiElement resolve = (PhpPsiElement) classReference.resolve(); if (resolve instanceof PhpClass) { return (PhpClass) resolve; } } } phpPsiElement = (PhpPsiElement) phpPsiElement.getParent(); } return null; } @Nullable public static PhpClass getPhpClassByCallChain(MethodReference methodRef) { while (methodRef != null) { PhpExpression expr = methodRef.getClassReference(); if (expr instanceof ClassReference) { return (PhpClass) ((ClassReference) expr).resolve(); } else if (expr instanceof MethodReference) { methodRef = (MethodReference) expr; } else if (expr instanceof Variable) { PhpType type = expr.getType(); String strType = type.toString(); int index1 = strType.indexOf('\\'); int index2 = strType.indexOf('.'); if (index2 == -1) index2 = strType.length() - index1; if (index1 >= 0 && index2 >= 0 && index2 > index1) { String className = strType.substring(index1, index2); return ClassUtils.getClass(PhpIndex.getInstance(methodRef.getProject()), className); } else { return null; } // type.toString() } else { return null; } } return null; } public static boolean isClassInheritsOrEqual(PhpClass classObject, String className, PhpIndex index) { PhpClass phpClass = ClassUtils.getClass(index, className); return isClassInheritsOrEqual(classObject, phpClass); } public static boolean isClassInheritsOrEqual(PhpClass classObject, PhpClass superClass) { if (classObject == null || superClass == null) return false; if (classObject.isEquivalentTo(superClass)) return true; else return isClassInheritsOrEqual(classObject.getSuperClass(), superClass); } public static boolean isClassInherit(PhpClass classObject, String className, PhpIndex index) { PhpClass phpClass = ClassUtils.getClass(index, className); return isClassInherit(classObject, phpClass); } public static boolean isClassInherit(PhpClass classObject, PhpClass superClass) { if (classObject == null || superClass == null) return false; PhpClass clazz = classObject.getSuperClass(); if (classObject.isEquivalentTo(superClass)) return true; else return isClassInherit(classObject.getSuperClass(), superClass); } public static String getAsPropertyName(Method method) { String methodName = method.getName(); String propertyName = methodName.substring(3); propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); return propertyName; } public static Collection<Method> getClassSetMethods(PhpClass phpClass) { final HashSet<Method> result = new HashSet<>(); final Collection<Method> methods = phpClass.getMethods(); for (Method method : methods) { String methodName = method.getName(); int pCount = method.getParameters().length; if (methodName.length() > 3 && methodName.startsWith("set") && pCount == 1 && Character.isUpperCase(methodName.charAt(3))) { result.add(method); } } return result; } @Nullable public static MethodReference getMethodRef(PsiElement el, int recursionLimit) { if (el == null) return null; else if (el.getParent() instanceof MethodReference) return (MethodReference) el.getParent(); else if (recursionLimit <= 0) return null; else return getMethodRef(el.getParent(), recursionLimit - 1); } @NotNull public static String getStringByElement(PsiElement element) { if (element instanceof StringLiteralExpression || element instanceof ConcatenationExpression) { return element.getText(); } else return ""; } public static String removeQuotes(@NotNull String str) { return str.replace("\"", "").replace("\'", ""); } public static boolean isFieldExists(PhpClass phpClass, String fieldName, boolean excludePhpDoc) { if (phpClass == null || fieldName == null) return false; fieldName = ClassUtils.removeQuotes(fieldName); final Field field = phpClass.findFieldByName(fieldName, false); if (field != null) { if ((field instanceof PhpDocProperty) && excludePhpDoc) { // skip DocProperty if excludePhpDoc = true } else { final PhpModifier modifier = field.getModifier(); return !(!modifier.isPublic() || modifier.isStatic()); } } final String methodName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); Method method = phpClass.findMethodByName("set" + methodName); if (method != null && !method.isStatic() && method.getAccess().isPublic() && method.getParameters().length == 1) { return true; } method = phpClass.findMethodByName("get" + methodName); return method != null && !method.isStatic() && method.getAccess().isPublic() && method.getParameters().length == 0; } @Nullable public static PhpClassMember findWritableField(PhpClass phpClass, String fieldName) { if (phpClass == null || fieldName == null) return null; fieldName = ClassUtils.removeQuotes(fieldName); final Collection<Field> fields = phpClass.getFields(); final Collection<Method> methods = phpClass.getMethods(); if (fields != null) { for (Field field : fields) { if (!field.getName().equals(fieldName)) continue; if (field.isConstant()) { continue; } final PhpModifier modifier = field.getModifier(); if (!modifier.isPublic() || modifier.isStatic()) { continue; } if (field instanceof PhpDocProperty && isReadonlyProperty(phpClass, (PhpDocProperty) field)) { break; } return field; } } if (methods != null) { for (Method method : methods) { String methodName = method.getName(); int pCount = method.getParameters().length; if (methodName.length() > 3 && methodName.startsWith("set") && pCount == 1 && Character.isUpperCase(methodName.charAt(3))) { String propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); if (propertyName.equals(fieldName)) return method; } } } return null; } /** * Get index for PsiElement which is child in ArrayCreationExpression or Method * @param psiElement * @return */ public static int indexForElementInParameterList(PsiElement psiElement) { PsiElement parent = psiElement.getParent(); if (parent == null) { return -1; } if (parent instanceof ParameterList) { return ArrayUtil.indexOf(((ParameterList) parent).getParameters(), psiElement); } return indexForElementInParameterList(parent); } public static Collection<Field> getWritableClassFields(PhpClass phpClass) { if (phpClass == null) return null; final HashSet<Field> result = new HashSet<>(); final Collection<Field> fields = phpClass.getFields(); for (Field field : fields) { if (field.isConstant()) { continue; } final PhpModifier modifier = field.getModifier(); if (!modifier.isPublic() || modifier.isStatic()) { continue; } if (field instanceof PhpDocProperty && isReadonlyProperty(phpClass, (PhpDocProperty) field)) { continue; } result.add(field); } return result; } public static Collection<Field> getClassFields(PhpClass phpClass) { if (phpClass == null) return null; final HashSet<Field> result = new HashSet<>(); final Collection<Field> fields = phpClass.getFields(); for (Field field : fields) { if (field.isConstant()) { continue; } final PhpModifier modifier = field.getModifier(); if (!modifier.isPublic() || modifier.isStatic()) { continue; } result.add(field); } return result; } public static boolean isReadonlyProperty(PhpClass clazz, PhpDocProperty field) { final String fieldName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1); return clazz.findMethodByName("set" + fieldName) == null && clazz.findMethodByName("get" + fieldName) != null; } @Nullable public static PhpClass findClassInSeeTags(PhpIndex index, PhpClass phpClass, String searchClassFQN) { if (phpClass.getDocComment() == null) return null; PhpClass activeRecordClass = null; PhpDocTag[] tags = phpClass.getDocComment().getTagElementsByName("@see"); for (PhpDocTag tag : tags) { String className = tag.getText().replace(tag.getName(), "").trim(); if (className.indexOf('\\') == -1) { className = phpClass.getNamespaceName() + className; } PhpClass classInSee = getClass(index, className); if (isClassInheritsOrEqual(classInSee, getClass(index, searchClassFQN))) { activeRecordClass = classInSee; break; } } return activeRecordClass; } @Nullable public static PhpClass getElementType(PhpNamedElement param) { Set<String> types = param.getType().getTypes(); PhpClass resultClass = null; for (String type : types) { // inherited phpdoc type // Example of type value: #A#M#C\yii\data\BaseDataProvider.setSort.0 if (type.indexOf("#A#M#C") != -1) { String ref = type.substring(6); String[] parts = ref.split("\\."); if (parts.length == 3) { resultClass = getClass(PhpIndex.getInstance(param.getProject()), parts[0]); if (resultClass != null) { Method method = resultClass.findMethodByName(parts[1]); try { int index = Integer.parseInt(parts[2]); if (method != null && method.getParameters().length > index) { return getElementType(method.getParameters()[index]); } } catch (NumberFormatException ex) { // pass } } } } else { resultClass = getClass(PhpIndex.getInstance(param.getProject()), type); if (resultClass != null && !resultClass.getName().equals("Closure") && !resultClass.getName().equals("Expression")) { return resultClass; } } } return null; } }