/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2014-2014 TiVo Inc.
* Copyright 2014-2014 AS3Boyan
* Copyright 2014-2014 Elias Ku
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.plugins.haxe.lang.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.plugins.haxe.HaxeComponentType;
import com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypes;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.model.HaxeClassModel;
import com.intellij.plugins.haxe.util.HaxeResolveUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.InheritanceImplUtil;
import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.PsiSuperMethodImplUtil;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.java.PsiTypeParameterListImpl;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.apache.log4j.Level;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author: Fedor.Korotkov
*/
public abstract class AbstractHaxePsiClass extends AbstractHaxeNamedComponent implements HaxeClass {
private static final Logger LOG = Logger.getInstance("#com.intellij.plugins.haxe.lang.psi.impl.AbstractHaxePsiClass");
static {
LOG.info("Loaded AbstractHaxePsiClass");
LOG.setLevel(Level.DEBUG);
}
public AbstractHaxePsiClass(@NotNull ASTNode node) {
super(node);
}
@Override
public HaxeNamedComponent getTypeComponent() {
return this;
}
@NotNull
@Override
public String getQualifiedName() {
String name = getName();
if (getParent() == null) {
return name == null ? "" : name;
}
if(name == null && this instanceof HaxeAnonymousType) {
// restore name from parent
final PsiElement typedefDecl = getParent().getParent();
if(typedefDecl != null && typedefDecl instanceof HaxeTypedefDeclaration) {
name = ((HaxeTypedefDeclaration)typedefDecl).getName();
}
}
final String fileName = FileUtil.getNameWithoutExtension(getContainingFile().getName());
String packageName = HaxeResolveUtil.getPackageName(getContainingFile());
if (name != null && isAncillaryClass(packageName, name, fileName)) {
packageName = HaxeResolveUtil.joinQName(packageName, fileName);
}
return HaxeResolveUtil.joinQName(packageName, name);
}
private HaxeClassModel _model = null;
public HaxeClassModel getModel() {
if (_model == null) _model = new HaxeClassModel(this);
return _model;
}
// check if class is declared inside haxe module `MyClass.MySupportType`
private boolean isAncillaryClass(@NotNull String packageName, @NotNull String name, @NotNull String fileName) {
// if file name matches type name
if(fileName.equals(name)) {
return false;
}
// if StdTypes
if(packageName.isEmpty() && fileName.equals("StdTypes")) {
return false;
}
// file contains valid type declaration
return HaxeResolveUtil.findComponentDeclaration(getContainingFile(), name) != null;
}
@Override
public boolean isExtern() {
return (this instanceof HaxeExternClassDeclaration || this instanceof HaxeExternInterfaceDeclaration);
}
@Override
public boolean isInterface() {
return HaxeComponentType.typeOf(this) == HaxeComponentType.INTERFACE;
}
@NotNull
@Override
public List<HaxeType> getHaxeExtendsList() {
return HaxeResolveUtil.findExtendsList(PsiTreeUtil.getChildOfType(this, HaxeInheritList.class));
}
@NotNull
@Override
public List<HaxeType> getHaxeImplementsList() {
return HaxeResolveUtil.getImplementsList(PsiTreeUtil.getChildOfType(this, HaxeInheritList.class));
}
@Override
public PsiElement add(@NotNull PsiElement element) throws IncorrectOperationException {
return super.add(element);
}
@NotNull
@Override
public List<HaxeMethod> getHaxeMethods() {
// XXX: This implementation is equivalent to getAllMethods(). That
// may not be what we want.
final List<HaxeNamedComponent> alltypes = HaxeResolveUtil.findNamedSubComponents(this);
final List<HaxeNamedComponent> methods = HaxeResolveUtil.filterNamedComponentsByType(alltypes, HaxeComponentType.METHOD);
final List<HaxeMethod> result = new ArrayList<HaxeMethod>();
for ( HaxeNamedComponent method : methods ) {
result.add((HaxeMethod)method);
}
return result;
}
@NotNull
@Override
public List<HaxeNamedComponent> getHaxeFields() {
final List<HaxeNamedComponent> result = HaxeResolveUtil.findNamedSubComponents(this);
return HaxeResolveUtil.filterNamedComponentsByType(result, HaxeComponentType.FIELD);
}
@NotNull
@Override
public List<HaxeVarDeclaration> getVarDeclarations() {
return HaxeResolveUtil.getClassVarDeclarations(this);
}
@Nullable
@Override
public HaxeNamedComponent findHaxeFieldByName(@NotNull final String name) {
return ContainerUtil.find(getHaxeFields(), new Condition<HaxeNamedComponent>() {
@Override
public boolean value(HaxeNamedComponent component) {
return name.equals(component.getName());
}
});
}
@Override
public HaxeNamedComponent findHaxeMethodByName(@NotNull final String name) {
return ContainerUtil.find(getHaxeMethods(), new Condition<HaxeNamedComponent>() {
@Override
public boolean value(HaxeNamedComponent component) {
return name.equals(component.getName());
}
});
}
@Override
public boolean isGeneric() {
return getGenericParam() != null;
}
@Override
public boolean isEnum() {
return (HaxeComponentType.typeOf(this) == HaxeComponentType.ENUM);
}
@Override
public boolean isAnnotationType() {
/* both: annotation & typedef in haxe are treated as typedef! */
return (HaxeComponentType.typeOf(this) == HaxeComponentType.TYPEDEF);
}
@Override
public boolean isDeprecated() {
/* not applicable to Haxe language */
return false;
}
@Override
@NotNull
public PsiClass[] getSupers() {
// Extends and Implements in one list
return PsiClassImplUtil.getSupers(this);
}
@Override
public PsiClass getSuperClass() {
return PsiClassImplUtil.getSuperClass(this);
}
@Override
@NotNull
public PsiClassType[] getSuperTypes() {
return PsiClassImplUtil.getSuperTypes(this);
}
@Override
public PsiElement getScope() {
String name = this.getName();
if (null == name || "".equals(name)) {
// anonymous class inherits containing class' search scope
return this.getContainingClass();
}
return this.getContainingFile();
}
@Override
public PsiClass getContainingClass() {
PsiElement parent = getParent();
return (parent instanceof PsiClass ? (PsiClass)parent : null);
}
@Override
public PsiClass[] getInterfaces() { // Extends and Implements in one list
return PsiClassImplUtil.getInterfaces(this);
}
@Override
@Nullable
public PsiReferenceList getExtendsList() {
// LOG.debug("\n>>>\tgetExtendsList();");
HaxeInheritList inh = PsiTreeUtil.getChildOfType(this, HaxeInheritList.class);
return null == inh ? null : PsiTreeUtil.getChildOfType(inh, HaxeExtendsDeclaration.class);
}
@Override
@NotNull
public PsiClassType[] getExtendsListTypes() {
final PsiReferenceList extendsList = this.getExtendsList();
if (extendsList != null) {
return extendsList.getReferencedTypes();
}
return PsiClassType.EMPTY_ARRAY;
}
@Override
@Nullable
public PsiReferenceList getImplementsList() {
HaxeInheritList inh = PsiTreeUtil.getChildOfType(this, HaxeInheritList.class);
return null == inh ? null : PsiTreeUtil.getChildOfType(inh, HaxeImplementsDeclaration.class);
}
@Override
@NotNull
public PsiClassType[] getImplementsListTypes() {
final PsiReferenceList implementsList = this.getImplementsList();
if (implementsList != null) {
return implementsList.getReferencedTypes();
}
return PsiClassType.EMPTY_ARRAY;
}
@Override
public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) {
return InheritanceImplUtil.isInheritor(this, baseClass, checkDeep);
}
@Override
public boolean isInheritorDeep(PsiClass baseClass, @Nullable PsiClass classToByPass) {
return InheritanceImplUtil.isInheritorDeep(this, baseClass, classToByPass);
}
@Override
@NotNull
public PsiClassInitializer[] getInitializers() {
// XXX: This may be needed during implementation of refactoring feature
// Needs change in BNF to detect initializer patterns, load them as accessible constructs in a class object
// For now, this will be empty
return PsiClassInitializer.EMPTY_ARRAY;
}
@Override
@NotNull
public HaxePsiField[] getFields() {
List<HaxeNamedComponent> haxeFields = getHaxeFields();
HaxePsiField[] psiFields = new HaxePsiField[haxeFields.size()];
return haxeFields.toArray(psiFields);
}
@Override
@NotNull
public PsiField[] getAllFields() {
return PsiClassImplUtil.getAllFields(this);
}
@Override
@Nullable
public PsiField findFieldByName(@NonNls String name, boolean checkBases) {
return PsiClassImplUtil.findFieldByName(this, name, checkBases);
}
@Override
@NotNull
public PsiMethod[] getMethods() {
final List<HaxeNamedComponent> alltypes = HaxeResolveUtil.getNamedSubComponents(this);
final List<HaxeNamedComponent> methods = HaxeResolveUtil.filterNamedComponentsByType(alltypes, HaxeComponentType.METHOD);
return methods.toArray(PsiMethod.EMPTY_ARRAY); // size is irrelevant
}
@Override
@NotNull
public PsiMethod[] getAllMethods() {
return PsiClassImplUtil.getAllMethods(this);
}
@Override
@NotNull
public PsiMethod[] getConstructors() {
return PsiClassImplUtil.findMethodsByName(this, HaxeTokenTypes.ONEW.toString(), false);
}
@Override
@Nullable
public PsiMethod findMethodBySignature(final PsiMethod psiMethod, final boolean checkBases) {
return PsiClassImplUtil.findMethodBySignature(this, psiMethod, checkBases);
}
@Override
@NotNull
public PsiMethod[] findMethodsByName(@NonNls String name, boolean checkBases) {
if ("main".equals(name)) { checkBases = false; }
return PsiClassImplUtil.findMethodsByName(this, name, checkBases);
}
@Override
@NotNull
public PsiMethod[] findMethodsBySignature(PsiMethod patternMethod, boolean checkBases) {
return PsiClassImplUtil.findMethodsBySignature(this, patternMethod, checkBases);
}
@Override
@NotNull
public List<Pair<PsiMethod, PsiSubstitutor>> getAllMethodsAndTheirSubstitutors() {
return PsiClassImplUtil.getAllWithSubstitutorsByMap(this, PsiClassImplUtil.MemberType.METHOD);
}
@Override
@NotNull
public List<Pair<PsiMethod, PsiSubstitutor>> findMethodsAndTheirSubstitutorsByName(@NonNls String name, boolean checkBases) {
return PsiClassImplUtil.findMethodsAndTheirSubstitutorsByName(this, name, checkBases);
}
@Override
public boolean hasTypeParameters() {
return PsiImplUtil.hasTypeParameters(this);
}
@Override
@Nullable
public PsiTypeParameterList getTypeParameterList() {
return new PsiTypeParameterListImpl(this.getNode());
}
@Override
@NotNull
public PsiTypeParameter[] getTypeParameters() {
return PsiImplUtil.getTypeParameters(this);
}
@Override
public PsiElement getLBrace() {
return findChildByRoleAsPsiElement(ChildRole.LBRACE);
}
@Override
public PsiElement getRBrace() {
return findChildByRoleAsPsiElement(ChildRole.RBRACE);
}
private boolean isPrivate() {
HaxePrivateKeyWord privateKeyWord = null;
if (this instanceof HaxeClassDeclaration) { // concrete class
privateKeyWord = ((HaxeClassDeclaration) this).getPrivateKeyWord();
}
else if (this instanceof HaxeAbstractClassDeclaration) { // abstract class
privateKeyWord = ((HaxeAbstractClassDeclaration) this).getPrivateKeyWord();
}
else if (this instanceof HaxeExternClassDeclaration) { // extern class
privateKeyWord = ((HaxeExternClassDeclaration)this).getPrivateKeyWord();
}
else if (this instanceof HaxeTypedefDeclaration) { // typedef
privateKeyWord = ((HaxeTypedefDeclaration) this).getPrivateKeyWord();
}
else if (this instanceof HaxeInterfaceDeclaration) { // interface
privateKeyWord = ((HaxeInterfaceDeclaration) this).getPrivateKeyWord();
}
else if (this instanceof HaxeEnumDeclaration) { // enum
privateKeyWord = ((HaxeEnumDeclaration) this).getPrivateKeyWord();
}
return (privateKeyWord != null);
}
@Override
public boolean isPublic() {
return (!isPrivate() && super.isPublic()); // do not change the order of- and the- expressions
}
@NotNull
@Override
public HaxeModifierList getModifierList() {
HaxeModifierList list = super.getModifierList();
if (null == list) {
list = new HaxeModifierListImpl(this.getNode());
}
// -- below modifiers need to be set individually
// because, they cannot be enforced through macro-list
if (isPrivate()) {
list.setModifierProperty(HaxePsiModifier.PRIVATE, true);
}
if (this instanceof HaxeAbstractClassDeclaration) { // is abstract class
list.setModifierProperty(HaxePsiModifier.ABSTRACT, true);
}
// XXX: Users of HaxeModifierList generally check for the existence of the property, not it's value.
// So, don't set it.
//list.setModifierProperty(HaxePsiModifier.STATIC, false); // Haxe does not have static classes, yet!
LOG.assertTrue(!list.hasModifierProperty(HaxePsiModifier.STATIC), "Haxe classes cannot be static.");
return list;
}
@Override
public boolean hasModifierProperty(@HaxePsiModifier.ModifierConstant @NonNls @NotNull String name) {
return this.getModifierList().hasModifierProperty(name);
}
@Override
@Nullable
public PsiDocComment getDocComment() {
// TODO: Fix 'public PsiDocComment getDocComment()'
//PsiComment psiComment = HaxeResolveUtil.findDocumentation(this);
//return ((psiComment != null)? new HaxePsiDocComment(this, psiComment) : null);
return null;
}
@Override
@NotNull
public PsiElement getNavigationElement() {
return this;
}
@Override
@Nullable
public PsiIdentifier getNameIdentifier() {
// For a HaxeClass, the identifier is three children below. The first is
// the component name, then a reference, and finally the identifier.
HaxeComponentName name = PsiTreeUtil.getChildOfType(this, HaxeComponentName.class);
return null == name ? null : name.getIdentifier();
}
@Override
@NotNull
public Collection<HierarchicalMethodSignature> getVisibleSignatures() {
return PsiSuperMethodImplUtil.getVisibleSignatures(this);
}
@Override
@NotNull
public PsiClass[] getInnerClasses() {
return PsiClass.EMPTY_ARRAY;
}
@Override
@NotNull
public PsiClass[] getAllInnerClasses() {
return PsiClass.EMPTY_ARRAY;
}
@Override
@Nullable
public PsiClass findInnerClassByName(@NonNls String name, boolean checkBases) {
return null;
}
public static AbstractHaxePsiClass EMPTY_FACADE = new AbstractHaxePsiClass(new HaxeDummyASTNode("EMPTY_FACADE")) {
@Nullable
@Override
public HaxeGenericParam getGenericParam() {
return null;
}
@Nullable
@Override
public HaxeComponentName getComponentName() {
return null;
}
};
@Override
public void delete() {
// FIX: for twice deletion of file in project view (issue #424)
final HaxeFile file = (HaxeFile)getContainingFile();
super.delete();
if(file != null && file.getClasses().length == 0) {
file.delete();
}
}
}