package com.siberika.idea.pascal.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.FileContentUtil;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.siberika.idea.pascal.lang.psi.*;
import com.siberika.idea.pascal.lang.psi.impl.PasClassParentImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasExportedRoutineImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasField;
import com.siberika.idea.pascal.lang.psi.impl.PasGenericTypeIdentImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasNamespaceIdentImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasRefNamedIdentImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasRoutineImplDeclImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasStructTypeImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasSubIdentImpl;
import com.siberika.idea.pascal.lang.psi.impl.PasTypeIDImpl;
import com.siberika.idea.pascal.lang.psi.impl.PascalExpression;
import com.siberika.idea.pascal.lang.psi.impl.PascalRoutineImpl;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Author: George Bakhtadze
* Date: 24/03/2013
*/
public class PsiUtil {
private static final Logger LOG = Logger.getInstance(PsiUtil.class.getName());
private static final int MAX_NON_BREAKING_NAMESPACES = 2;
private static final long MIN_REPARSE_INTERVAL = 2000;
public static final String TYPE_UNTYPED_NAME = "<untyped>";
private static long lastReparseRequestTime = System.currentTimeMillis() - MIN_REPARSE_INTERVAL;
@NotNull
public static <T extends PsiElement> Collection<T> findChildrenOfAnyType(@Nullable final PsiElement element,
@NotNull final Class<? extends T>... classes) {
if (element == null) {
return ContainerUtil.emptyList();
}
PsiElementProcessor.CollectElements<T> processor = new PsiElementProcessor.CollectElements<T>() {
@Override
public boolean execute(@NotNull T each) {
if (each == element) return true;
if (PsiTreeUtil.instanceOf(each, classes)) {
return super.execute(each);
}
return true;
}
};
PsiTreeUtil.processElements(element, processor);
return processor.getCollection();
}
@NotNull
@SuppressWarnings("unchecked")
public static <T extends PsiElement> Collection<T> findImmChildrenOfAnyType(@Nullable final PsiElement element,
@NotNull final Class<? extends T>... classes) {
if (element == null) {
return ContainerUtil.emptyList();
}
Collection<T> result = new SmartList<T>();
for (PsiElement each : element.getChildren()) {
if (PsiTreeUtil.instanceOf(each, classes)) {
result.add((T) each);
}
}
return result;
}
@Nullable
@SuppressWarnings("unchecked")
public static <T extends PsiElement> T findImmChildOfAnyType(@Nullable final PsiElement element, @NotNull final Class<? extends T>... classes) {
if (element != null) {
for (PsiElement each : element.getChildren()) {
if (PsiTreeUtil.instanceOf(each, classes)) {
return (T) each;
}
}
}
return null;
}
/**
* Searches for a previous sibling element ignoring whitespace elements
*
* @param element - element from where to search
* @return previous sibling element or null if not found
*/
@Nullable
public static PsiElement getPrevSibling(@NotNull PsiElement element) {
PsiElement result = element.getPrevSibling();
while (result instanceof PsiWhiteSpace) {
result = result.getPrevSibling();
}
return result;
}
/**
* Searches for a next sibling element ignoring whitespace elements
* @param element - element from where to search
* @return next sibling element or null if not found
*/
@Nullable
public static PsiElement getNextSibling(@NotNull PsiElement element) {
PsiElement result = element.getNextSibling();
while (result instanceof PsiWhiteSpace) {
result = result.getNextSibling();
}
return result;
}
/**
* Searches for element within parent by the given offset skipping whitespaces and comments
* @param parent parent element
* @param offset offset within parent
* @return element or null if not found
*/
@Nullable
public static PsiElement findElementAt(@NotNull PsiElement parent, int offset) {
PsiElement result = parent.findElementAt(offset);
if (PsiTreeUtil.instanceOf(result, PsiWhiteSpace.class, PsiComment.class)) {
result = PsiTreeUtil.skipSiblingsForward(result, PsiWhiteSpace.class, PsiComment.class);
}
return result;
}
/**
* Searches for sibling element which contains "end" text
* @param element - element from where to search
* @return next sibling element or null if not found
*/
@Nullable
public static PsiElement findEndSibling(PsiElement element) {
PsiElement result = element.getNextSibling();
while ((result != null) && !((result instanceof LeafPsiElement) && ("end".equalsIgnoreCase(result.getText())))) {
result = result.getNextSibling();
}
return result;
}
/**
* Returns nearest declarations root element which affects the given element, including formal parameters clause,
* declaration section, structured type declaration, module declaration
*
* @param element - element which should be affected by declarations found
* @return nearest declarations root element which affects the given element
*/
@SuppressWarnings("unchecked")
public static PsiElement getNearestAffectingDeclarationsRoot(PsiElement element) {
if (element instanceof PasUnitImplementation) {
PasUnitInterface unitInterface = PsiTreeUtil.getPrevSiblingOfType(element, PasUnitInterface.class);
if (unitInterface != null) {
return unitInterface;
}
}
PascalPsiElement parent = getParentDeclRoot(element);
if (isNamedIdent(element) &&
// Make routine itself belong to parent root
((parent instanceof PascalRoutineImpl) && (element.getParent() == parent))
// Make class parent belong to parent root
|| ((parent instanceof PasEntityScope) && (element.getParent().getParent().getClass() == PasClassParentImpl.class))) {
return getNearestAffectingDeclarationsRoot(parent);
} else if (PsiUtil.isInstanceOfAny(parent, PasFormalParameterSection.class, PasBlockGlobal.class)) {
// Make routine local variable or parameters belong to the routine
return getParentDeclRoot(parent);
}
return parent;
}
private static boolean isNamedIdent(PsiElement element) {
return (element instanceof PasNamedIdent) || (element instanceof PasClassQualifiedIdent);
}
@SuppressWarnings("unchecked")
private static PascalPsiElement getParentDeclRoot(PsiElement element) { // TODO: remove blocks?
return PsiTreeUtil.getParentOfType(element,
PascalRoutineImpl.class, PasFormalParameterSection.class,
PasClosureExpr.class,
PasUnitImplementation.class,
PasBlockGlobal.class,
PasModule.class,
PasUnitInterface.class,
PasEntityScope.class
);
}
/**
* Returns nearest scope which affecting the element
*/
@Nullable
public static PasEntityScope getDeclRootScope(PsiElement element) {
if (null == element) {
return null;
} else if (element instanceof PasEntityScope) {
return (PasEntityScope) element;
} else {
return getDeclRootScope(getParentDeclRoot(element));
}
}
public static String getElDebugContext(PsiElement current) {
return current != null ? "\"" + (current instanceof PascalNamedElement ? ((PascalNamedElement) current).getName() : "")
+ "\" [" + current.getClass().getSimpleName() + "]" + getParentStr(current.getParent()) : "-";
}
private static String getParentStr(@NotNull PsiElement parent) {
return parent.getText() + " [" + parent.getClass().getSimpleName() + "]";
}
public static boolean isEntityName(@NotNull PsiElement element) {
return (element.getClass() == PasSubIdentImpl.class) || (element.getClass() == PasRefNamedIdentImpl.class);
}
public static boolean isTypeName(@NotNull PsiElement element) {
if (checkClass(element, PasGenericTypeIdentImpl.class)) {
return true;
}
PsiElement el = PsiTreeUtil.skipParentsOfType(element, PasSubIdent.class, PasFullyQualifiedIdent.class, PsiWhiteSpace.class, PsiErrorElement.class);
return checkClass(el, PasGenericTypeIdentImpl.class) || checkClass(el, PasTypeIDImpl.class);
}
private static boolean checkClass(PsiElement element, Class clazz) {
return (element != null) && (element.getClass() == clazz);
}
public static boolean isRoutineName(@NotNull PascalNamedElement element) {
return element.getParent() instanceof PascalRoutineImpl;
}
public static boolean isUsedUnitName(@NotNull PsiElement element) {
return (element instanceof PasNamespaceIdent) && (element.getParent() instanceof PasUsesClause);
}
/**
* @param element element
* @return true if the element is a pointer type declaration. I.e. "TType = ^element".
*/
public static boolean isPointerTypeDeclaration(@NotNull PascalNamedElement element) {
return PsiTreeUtil.skipParentsOfType(element, PasSubIdent.class, PasFullyQualifiedIdent.class, PasTypeIDImpl.class, PasTypeDecl.class) instanceof PasPointerType;
}
/**
* @param element element
* @return true if the element is a class reference type declaration. I.e. "CClass = class of TSomeClass".
*/
public static boolean isClassRefDeclaration(@NotNull PascalNamedElement element) {
return PsiTreeUtil.skipParentsOfType(element, PasSubIdent.class, PasFullyQualifiedIdent.class, PasTypeIDImpl.class, PasTypeDecl.class) instanceof PasClassTypeTypeDecl;
}
public static boolean isFormalParameterName(@NotNull PascalNamedElement element) {
return element.getParent() instanceof PasFormalParameter;
}
public static <T extends PsiElement> boolean isInstanceOfAny(Object object, Class... classes) {
int i = classes.length - 1;
while ((i >= 0) && (!classes[i].isInstance(object))) {
i--;
}
return i >= 0;
}
/**
* Returns module containing the element
*
* @param element - element
* @return module containing the element
*/
@Nullable
public static PasModule getElementPasModule(@NotNull PsiElement element) {
return PsiTreeUtil.findChildOfType(element.getContainingFile(), PasModule.class);
}
/**
* Returns interface section of module specified by section
*
* @param section - can be PasModule or PsiFile
* @return interface section of module
*/
@Nullable
public static PsiElement getModuleInterfaceSection(@NotNull PsiElement section) {
assert (section instanceof PasModule) || (section instanceof PsiFile);
return PsiTreeUtil.findChildOfType(section, PasUnitInterface.class);
}
/**
* Returns implementation section of module specified by section
*
* @param section - can be PasModule or PsiFile
* @return unit implementation section or module itself if the module is not a unit
*/
@NotNull
public static PsiElement getModuleImplementationSection(@NotNull PsiElement section) {
assert (section instanceof PasModule) || (section instanceof PsiFile);
PsiElement result = PsiTreeUtil.findChildOfType(section, PasUnitImplementation.class);
if (result == null) {
result = section;
}
return result;
}
@NotNull
@SuppressWarnings("unchecked")
public static List<PascalQualifiedIdent> getUsedUnits(PsiElement parent) {
List<PascalQualifiedIdent> result = new SmartList<PascalQualifiedIdent>();
Collection<PasUsesClause> usesClauses = findChildrenOfAnyType(parent, PasUsesClause.class);
for (PasUsesClause usesClause : usesClauses) {
for (PsiElement usedUnitName : usesClause.getChildren()) {
if (usedUnitName.getClass() == PasNamespaceIdentImpl.class) {
result.add((PasNamespaceIdent) usedUnitName);
}
}
}
return result;
}
public static int getElementLevel(PsiElement element) {
int result = 0;
while ((element != null) && (!(element instanceof PsiFile))) {
result++;
element = element.getParent();
}
return result;
}
public static boolean isSameAffectingScope(PsiElement innerSection, PsiElement outerSection) {
for (int i = 0; i < MAX_NON_BREAKING_NAMESPACES; i++) {
if ((innerSection == outerSection) || sameModuleSections(innerSection, outerSection)) {
return true;
}
//noinspection unchecked
if ((null == innerSection) || PsiUtil.isInstanceOfAny(innerSection, PasEntityScope.class, PasClosureExpr.class)) {
return false;
}
innerSection = PsiUtil.getNearestAffectingDeclarationsRoot(innerSection);
}
return false;
}
private static boolean sameModuleSections(PsiElement innerSection, PsiElement outerSection) {
return (outerSection instanceof PasModule) && (innerSection instanceof PasUnitInterface) && (((PasModule) outerSection).getUnitInterface() == innerSection);
}
@NotNull
public static List<PasNamedIdent> getFormalParameters(PasFormalParameterSection paramsSection) {
if (paramsSection != null) {
List<PasNamedIdent> result = new SmartList<PasNamedIdent>();
for (PasFormalParameter parameter : paramsSection.getFormalParameterList()) {
for (PasNamedIdent ident : parameter.getNamedIdentList()) {
result.add(ident);
}
}
return result;
}
return Collections.emptyList();
}
@Nullable
public static <T extends PsiElement> T findInSameSection(PsiElement section, Class<? extends T>... classes) {
T element = PsiTreeUtil.findChildOfAnyType(section, classes);
if ((element != null) && (isSameAffectingScope(getNearestAffectingDeclarationsRoot(element), section))) {
return element;
}
return null;
}
public static void rebuildPsi(PsiElement block) {
LOG.info("WARNING: requesting reparse: " + block);
if (System.currentTimeMillis() - lastReparseRequestTime >= MIN_REPARSE_INTERVAL) {
lastReparseRequestTime = System.currentTimeMillis();
//BlockSupport.getInstance(block.getProject()).reparseRange(block.getContainingFile(), block.getTextRange().getStartOffset(), block.getTextRange().getEndOffset(), block.getText());
ApplicationManager.getApplication().invokeLater(
new Runnable() {
@Override
public void run() {
FileContentUtil.reparseOpenedFiles();
}
}
);
}
}
public static boolean isFieldDecl(PascalNamedElement entityDecl) {
return (entityDecl.getParent() instanceof PasClassField);
}
public static boolean isPropertyDecl(PascalNamedElement entityDecl) {
return (entityDecl.getParent() instanceof PasClassProperty);
}
/**
* Checks if the entityDecl is a declaration of variable or formal parameter
*
* @param entityDecl entity declaration to check
* @return true if the entityDecl is a declaration of variable or formal parameter
*/
public static boolean isVariableDecl(PascalNamedElement entityDecl) {
return (entityDecl.getParent() instanceof PascalVariableDeclaration);
}
// Checks if the entityDecl is a declaration of constant
public static boolean isConstDecl(PascalNamedElement entityDecl) {
return (entityDecl.getParent() instanceof PasConstDeclaration);
}
// Checks if the entityDecl is a declaration of an enumeration constant
public static boolean isEnumDecl(PascalNamedElement entityDecl) {
return (entityDecl.getParent() instanceof PasEnumType);
}
/**
* Returns type identifier element by entity declaration
*/
@Nullable
public static PascalNamedElement getEntityType(PascalNamedElement entityDecl) {
if (PsiUtil.isVariableDecl(entityDecl) || PsiUtil.isFieldDecl(entityDecl) || PsiUtil.isPropertyDecl(entityDecl)) { // variable declaration case
PascalPsiElement varDecl = PsiTreeUtil.getNextSiblingOfType(entityDecl, PasTypeDecl.class);
if (null == varDecl) {
varDecl = PsiTreeUtil.getNextSiblingOfType(entityDecl, PasTypeID.class);
}
if (varDecl != null) {
return PsiTreeUtil.findChildOfType(varDecl, PascalQualifiedIdent.class, true);
}
} else if (entityDecl.getParent() instanceof PasTypeDeclaration) { // type declaration case
return entityDecl;
} else if (entityDecl.getParent() instanceof PascalRoutineImpl) { // routine declaration case
PasTypeID type = ((PascalRoutineImpl) entityDecl.getParent()).getFunctionTypeIdent();
return type != null ? type.getFullyQualifiedIdent() : null;
}
return null;
}
public static boolean isForwardProc(PascalRoutineImpl decl) {
return PsiTreeUtil.findChildOfType(decl, PasProcForwardDecl.class) != null;
}
public static boolean allowsForwardReference(PsiElement element) {
return (element instanceof PascalNamedElement) &&
(PsiUtil.isPointerTypeDeclaration((PascalNamedElement) element) || PsiUtil.isClassRefDeclaration((PascalNamedElement) element));
}
public static boolean isStructureMember(PsiElement element) {
return (element.getParent() != null) && (element.getParent() instanceof PasStructTypeImpl);
}
public static PascalStructType getStructTypeByName(PsiElement name) {
PasTypeDecl typeDecl = PsiTreeUtil.getNextSiblingOfType(name, PasTypeDecl.class);
return typeDecl != null ? PsiTreeUtil.findChildOfType(typeDecl, PascalStructType.class) : null;
}
public static String getQualifiedMethodName(PsiNamedElement element) {
if (PsiUtil.isStructureMember(element)) {
PasStructTypeImpl owner = PasStructTypeImpl.findOwnerStruct(element);
if (null != owner) {
return getQualifiedMethodName(owner) + "." + element.getName();
}
}
return element != null ? element.getName() : "";
}
public static boolean isIdent(PsiElement element) {
return element instanceof PasSubIdent;
}
public static boolean isElementValid(PsiElement element) {
return element.isValid();
//return (ModuleUtilCore.findModuleForPsiElement(element) != null);
}
public static boolean isElementUsable(PsiElement element) {
return (element != null) && isElementValid(element);
}
//--------------------------------------------------------------------------------------------------------------
public static boolean isFromSystemUnit(PsiElement element) {
return (element.getContainingFile() != null) && "$system.pas".equalsIgnoreCase(element.getContainingFile().getName());
}
public static boolean isFromBuiltinsUnit(PsiElement element) {
return (element.getContainingFile() != null) && "$builtins.pas".equalsIgnoreCase(element.getContainingFile().getName());
}
public static boolean isForwardClassDecl(PascalNamedElement element) {
LeafPsiElement leaf1 = getLeafSiblingOfType(element, LeafPsiElement.class);
LeafPsiElement leaf2 = leaf1 != null ? getLeafSiblingOfType(leaf1, LeafPsiElement.class) : null;
return ((leaf2 != null) && ((leaf2.getElementType() == PasTypes.CLASS) || (leaf2.getElementType() == PasTypes.INTERFACE)));
}
public static <T extends PsiElement> T getLeafSiblingOfType(@Nullable PsiElement sibling, @NotNull Class<T> aClass) {
T result = PsiTreeUtil.getNextSiblingOfType(sibling, aClass);
while ((result != null) && (TreeUtil.isWhitespaceOrComment(result.getNode()))) {
result = PsiTreeUtil.getNextSiblingOfType(result, aClass);
}
return result;
}
public static boolean isTypeDeclPointingToSelf(@NotNull PascalNamedElement typeIdent) {
PsiElement parent = typeIdent.getParent();
if (parent instanceof PasTypeDeclaration) {
PasTypeDecl typeDecl = ((PasTypeDeclaration) parent).getTypeDecl();
PasTypeID typeId = typeDecl != null ? typeDecl.getTypeID() : null;
if (typeId != null) {
return typeIdent.getName().equalsIgnoreCase(typeId.getFullyQualifiedIdent().getName());
}
}
return false;
}
public static boolean isFromLibrary(@NotNull PsiElement element) {
return !element.isPhysical();
}
public static PasEntityScope getNearestAffectingScope(PsiElement element) {
PasClassParent parent = PsiTreeUtil.getParentOfType(element, PasClassParent.class);
if (parent != null) {
PascalStructType struct = PsiTreeUtil.getParentOfType(element, PascalStructType.class);
element = struct != null ? struct : element;
}
return PsiTreeUtil.getParentOfType(element, PasEntityScope.class);
}
public static PsiElement getNearestSection(PsiElement element) {
return PsiTreeUtil.getParentOfType(element, PasEntityScope.class, PasUnitInterface.class, PasUnitImplementation.class, PasBlockGlobal.class);
}
// returns name element of type with which is declared the specified field or routine
@Nullable
public static PasFullyQualifiedIdent getTypeNameIdent(PascalNamedElement element) {
PasTypeDecl typeDecl;
if ((element instanceof PasExportedRoutine) && (element.getFirstChild() != null)) { // resolve function type
typeDecl = PsiTreeUtil.getNextSiblingOfType(element.getFirstChild(), PasTypeDecl.class);
} else {
typeDecl = PsiTreeUtil.getNextSiblingOfType(element, PasTypeDecl.class);
}
PasTypeID typeId = typeDecl != null ? typeDecl.getTypeID() : null;
if (null == typeId) { // immediate complex non-structured type
typeId = PsiTreeUtil.getChildOfType(typeDecl != null ? typeDecl : element, PasTypeID.class);
}
return typeId != null ? typeId.getFullyQualifiedIdent() : null;
}
// returns type declaration for the specified field or routine element
@Nullable
public static PasTypeDecl getTypeDeclaration(PascalNamedElement element) {
PasTypeDecl typeDecl;
if ((element instanceof PascalRoutineImpl) && (element.getFirstChild() != null)) { // resolve function type
typeDecl = PsiTreeUtil.getNextSiblingOfType(element.getFirstChild(), PasTypeDecl.class);
} else {
typeDecl = PsiTreeUtil.getNextSiblingOfType(element, PasTypeDecl.class);
}
return typeDecl;
}
// returns type name which referenced in type declaration or null if type is anonymous
@Nullable
public static PasTypeID getDeclaredTypeName(@Nullable PasTypeDecl typeDecl) {
PasTypeID typeId = typeDecl != null ? typeDecl.getTypeID() : null;
if (null == typeId) { // immediate complex non-structured type
typeId = PsiTreeUtil.getChildOfType(typeDecl, PasTypeID.class);
}
return typeId;
}
public static boolean checkeElement(PsiElement element) {
if (!isElementValid(element)) {
//rebuildPsi(element);
return false;
//throw new PasInvalidScopeException(this);
}
return true;
}
public static String getFieldName(PascalNamedElement element) {
if (element instanceof PascalRoutineImpl) {
return normalizeRoutineName((PascalRoutineImpl) element);
} else {
return element.getName();
}
}
@NotNull
public static String getContainingFilePath(@Nullable PsiElement element) {
PsiFile file = element != null ? element.getContainingFile() : null;
VirtualFile vFile = file != null ? file.getVirtualFile() : null;
return vFile != null ? vFile.getPath() : "";
}
public static String normalizeRoutineName(PascalRoutineImpl routine) {
StringBuilder res = new StringBuilder(routine.getName());
PasFormalParameterSection params = routine.getFormalParameterSection();
if (params != null) {
res.append("(");
boolean nonFirst = false;
for (PasFormalParameter param : params.getFormalParameterList()) {
PasTypeDecl td = param.getTypeDecl();
String typeStr = TYPE_UNTYPED_NAME;
if (td != null) {
typeStr = td.getText();
}
for (int i = 0; i < param.getNamedIdentList().size(); i++) {
res.append(nonFirst ? "," : "").append(typeStr);
nonFirst = true;
}
}
res.append(")");
//name = name + (params != null ? params.getText() : "");
//name = name.replaceAll("\\s+", " ");
} else {
res.append("()");
}
String typeStr = routine.getFunctionTypeStr();
if (typeStr.length() > 0) {
res.append(":").append(typeStr);
}
return res.toString();
}
public static boolean isParentOf(PsiElement element, PsiElement parent) {
while ((element != null) && (element != parent)) {
element = element.getParent();
}
return element != null;
}
public static boolean isClassParent(PsiElement element) {
PasClassParent parent = PsiTreeUtil.getParentOfType(element, PasClassParent.class);
PasGenericPostfix gen = PsiTreeUtil.getParentOfType(element, PasGenericPostfix.class);
return (parent != null) && !isParentOf(gen, parent);
}
// Returns structured type declaration by any of its contained element or name element
public static PascalStructType getStructByElement(PsiElement element) {
PascalNamedElement named;
if (element instanceof PascalNamedElement) {
named = (PascalNamedElement) element;
} else {
named = PsiTreeUtil.getParentOfType(element, PascalNamedElement.class);
}
PascalStructType struct = getStructTypeByName(named);
return struct != null ? struct : PsiTreeUtil.getParentOfType(element, PascalStructType.class);
}
public static boolean needImplementation(PasExportedRoutineImpl routine) {
if (routine.getExternalDirective() != null) {
return false;
}
if (routine.getContainingScope() instanceof PasInterfaceTypeDecl) {
return false;
}
for (PasFunctionDirective dir : routine.getFunctionDirectiveList()) {
if (dir.getText().toUpperCase().startsWith("ABSTRACT")) {
return false;
}
}
return true;
}
public static String cleanGenericDef(String name) {
int ind = name.indexOf('<');
return ind < 0 ? name : name.substring(0, ind);
}
public static <T extends PsiElement> Collection<T> extractSmartPointers(List<SmartPsiElementPointer<T>> smartPtrs) {
Collection<T> res = new ArrayList<T>(smartPtrs.size());
for (SmartPsiElementPointer<T> smartPtr : smartPtrs) {
res.add(smartPtr.getElement());
}
return res;
}
@Nullable
public static PascalNamedElement getDefaultProperty(PasEntityScope typeScope) {
for (PasField field : typeScope.getAllFields()) {
if (field.fieldType == PasField.FieldType.PROPERTY) {
PascalNamedElement el = field.getElement();
if ((el != null) && (el.getNode().findChildByType(PasTypes.DEFAULT) != null)) {
return el;
}
}
}
return null;
}
public static PsiElement skipToExpressionParent(PsiElement element) {
return PsiTreeUtil.skipParentsOfType(element,
PasSubIdent.class, PasFullyQualifiedIdent.class, PasRefNamedIdent.class, PasNamedIdent.class, PasNamespaceIdent.class, PasGenericTypeIdent.class,
PasExpression.class, PascalExpression.class, PasExpressionOrd.class, PasConstExpression.class,
PsiWhiteSpace.class, PsiErrorElement.class,
PasUnitModuleHead.class);
}
public static PsiElement skipToExpression(PsiElement element) {
return PsiTreeUtil.skipParentsOfType(element,
PasSubIdent.class, PasFullyQualifiedIdent.class, PasRefNamedIdent.class, PasNamedIdent.class, PasGenericTypeIdent.class,
PsiWhiteSpace.class, PsiErrorElement.class,
PasUnitModuleHead.class);
}
public static boolean isLastPartOfMethodImplName(PascalNamedElement element) {
PsiElement parent = element.getParent();
if (parent instanceof PasClassQualifiedIdent) {
PasClassQualifiedIdent name = (PasClassQualifiedIdent) parent;
return (element == name.getSubIdentList().get(name.getSubIdentList().size() - 1))
&& isRoutineName((PascalNamedElement) parent) && !StringUtils.isEmpty(((PascalNamedElement) parent).getNamespace());
}
return false;
}
// returns unique name of ident qualifying it with scopes
public static String getUniqueName(PascalNamedElement ident) {
StringBuilder sb = new StringBuilder(ident.getName());
PasEntityScope scope = getNearestAffectingScope(ident);
while (scope != null) {
sb.append(".").append(PsiUtil.getFieldName(scope));
scope = scope.getContainingScope();
}
return sb.toString();
}
public static boolean belongsToInterface(PsiElement ident) {
return PsiTreeUtil.getParentOfType(ident, PasUnitInterface.class) != null;
}
// Returns pair sorted by starting offset. Nulls come last.
public static Pair<PsiElement, PsiElement> sortByStart(PsiElement o1, PsiElement o2, boolean ascending) {
if (null == o2) {
return Pair.create(o1, null);
}
if (null == o1) {
return Pair.create(o2, null);
}
boolean less = o1.getTextRange().getStartOffset() <= o2.getTextRange().getStartOffset();
less = ascending ? less : !less;
if (less) {
return Pair.create(o1, o2);
} else {
return Pair.create(o2, o1);
}
}
@NotNull
public static PsiContext getContext(@NotNull PascalNamedElement element) {
PascalNamedElement fqn = element;
PascalQualifiedIdent fqi = getFQI(element);
int count = 1;
int index = 0;
if (fqi != null) {
fqn = fqi;
if (element instanceof PasSubIdent) {
index = fqi.getSubIdentList().indexOf(element);
count = fqi.getSubIdentList().size();
}
}
if (index == count - 1) { // Last part of FQN
if ((element instanceof PasGenericTypeIdent) && (element.getParent() instanceof PasTypeDeclaration)) {
return PsiContext.TYPE_DECL;
} else if (isTypeName(element)) {
return PsiContext.TYPE_ID;
} else if (fqn.getParent() instanceof PasClassPropertySpecifier) {
return PsiContext.PROPERTY_SPEC; // False positives possible
} else if (fqn.getParent() instanceof PasGenericDefinition) {
return PsiContext.GENERIC_PARAM;
} else if (fqn.getParent() instanceof PasForStatement) {
return PsiContext.FOR;
} else if ((fqn.getParent() instanceof PasUsesClause) || (fqn.getParent() instanceof PasRequiresClause)) {
return PsiContext.USES;
} else if (fqn.getParent() instanceof PasExportsSection) {
return PsiContext.EXPORT;
} else {
PasExpr expr = PsiTreeUtil.getParentOfType(element, PasExpr.class);
if ((expr != null) && (expr.getNextSibling() instanceof PasArgumentList)) {
return PsiContext.CALL;
}
}
}
if (fqi != null) {
if (1 == count) {
return PsiContext.FQN_SINGLE;
} else if (0 == index) {
return PsiContext.FQN_FIRST;
} else {
return PsiContext.FQN_NEXT;
}
}
return PsiContext.UNKNOWN;
}
// Returns FQI which element is a part of
public static PascalQualifiedIdent getFQI(PsiElement element) {
if (element instanceof PascalQualifiedIdent) {
return (PascalQualifiedIdent) element;
}
PsiElement sub = (element instanceof PasSubIdent) ? element : element.getParent();
if (sub != null) {
return (sub.getParent() instanceof PascalQualifiedIdent) ? (PascalQualifiedIdent) sub.getParent() : null;
}
return null;
}
private static String normalizeGenericName(PasGenericTypeIdent ident) {
StringBuilder res = new StringBuilder();
PasGenericDefinition gen = ident.getGenericDefinition();
if (gen != null) {
for (PasNamedIdent type : gen.getNamedIdentList()) {
if (res.length() > 0) {
res.append(",");
}
res.append(type.getName());
}
}
if (res.length() > 0) {
res.insert(0, "<").insert(0, ident.getRefNamedIdent().getName()).append(">");
} else {
return ident.getRefNamedIdent().getName();
}
return res.toString();
}
// Check if the named element is the left part of an assignment statement
public static boolean isAssignLeftPart(PascalNamedElement element) {
PsiElement expr = PsiUtil.skipToExpression(element);
if (expr instanceof PasReferenceExpr) {
return (expr.getParent() instanceof PasExpression) && (expr.getParent().getParent() instanceof PasStatement);
}
return false;
}
public static boolean hasParameters(PascalRoutineImpl routine) {
PasFormalParameterSection params = routine.getFormalParameterSection();
return (params != null) && !params.getFormalParameterList().isEmpty();
}
public static boolean isPropertyGetter(PasClassPropertySpecifier spec) {
return "read".equalsIgnoreCase(spec.getFirstChild().getText());
}
public static boolean isBefore(@NotNull PsiElement el1, @NotNull PsiElement el2) {
return el1.getTextRange().getStartOffset() < el2.getTextRange().getStartOffset();
}
public static boolean isNotNestedRoutine(PascalRoutineImpl routine) {
return routine.getClass() == PasRoutineImplDeclImpl.class;
}
}