blob: ddaeeb7581b51c66180ccc974bfe46c3d8f31221 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.
*/
/*
* Created by IntelliJ IDEA.
* User: max
* Date: Oct 21, 2001
* Time: 4:29:19 PM
* To change template for new class use
* Code Style | Class Templates options (Tools | IDE Options).
*/
package com.intellij.codeInspection.reference;
import com.intellij.codeInsight.TestFrameworks;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.psi.*;
import com.intellij.psi.util.ClassUtil;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class RefClassImpl extends RefJavaElementImpl implements RefClass {
private static final Set<RefElement> EMPTY_SET = Collections.emptySet();
private static final Set<RefClass> EMPTY_CLASS_SET = Collections.emptySet();
private static final List<RefMethod> EMPTY_METHOD_LIST = ContainerUtil.emptyList();
private static final int IS_ANONYMOUS_MASK = 0x10000;
private static final int IS_INTERFACE_MASK = 0x20000;
private static final int IS_UTILITY_MASK = 0x40000;
private static final int IS_ABSTRACT_MASK = 0x80000;
private static final int IS_APPLET_MASK = 0x200000;
private static final int IS_SERVLET_MASK = 0x400000;
private static final int IS_TESTCASE_MASK = 0x800000;
private static final int IS_LOCAL_MASK = 0x1000000;
private static final int IS_ANDROID_MASK = 0x2000000;
private Set<RefClass> myBases; // singleton (to conserve the memory) or THashSet
private Set<RefClass> mySubClasses; // singleton (to conserve the memory) or THashSet
private List<RefMethod> myConstructors;
private RefMethodImpl myDefaultConstructor;
private List<RefMethod> myOverridingMethods;
private Set<RefElement> myInTypeReferences;
private Set<RefElement> myInstanceReferences;
private List<RefJavaElement> myClassExporters;
RefClassImpl(PsiClass psiClass, RefManager manager) {
super(psiClass, manager);
}
@Override
protected void initialize() {
myDefaultConstructor = null;
final PsiClass psiClass = getElement();
LOG.assertTrue(psiClass != null);
PsiElement psiParent = psiClass.getParent();
if (psiParent instanceof PsiFile) {
if (isSyntheticJSP()) {
final RefFileImpl refFile = (RefFileImpl)getRefManager().getReference(getJspFile(psiClass));
LOG.assertTrue(refFile != null);
refFile.add(this);
} else if (psiParent instanceof PsiJavaFile) {
PsiJavaFile psiFile = (PsiJavaFile) psiParent;
String packageName = psiFile.getPackageName();
if (!"".equals(packageName)) {
((RefPackageImpl)getRefJavaManager().getPackage(packageName)).add(this);
} else {
((RefPackageImpl)getRefJavaManager().getDefaultPackage()).add(this);
}
}
final Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
LOG.assertTrue(module != null);
final RefModuleImpl refModule = (RefModuleImpl)getRefManager().getRefModule(module);
LOG.assertTrue(refModule != null);
refModule.add(this);
} else {
while (!(psiParent instanceof PsiClass || psiParent instanceof PsiMethod || psiParent instanceof PsiField)) {
psiParent = psiParent.getParent();
}
RefElement refParent = getRefManager().getReference(psiParent);
LOG.assertTrue (refParent != null);
((RefElementImpl)refParent).add(this);
}
setAbstract(psiClass.hasModifierProperty(PsiModifier.ABSTRACT));
setAnonymous(psiClass instanceof PsiAnonymousClass);
setIsLocal(!(isAnonymous() || psiParent instanceof PsiClass || psiParent instanceof PsiFile));
setInterface(psiClass.isInterface());
initializeSuperReferences(psiClass);
PsiMethod[] psiMethods = psiClass.getMethods();
PsiField[] psiFields = psiClass.getFields();
setUtilityClass(psiMethods.length > 0 || psiFields.length > 0);
for (PsiField psiField : psiFields) {
getRefManager().getReference(psiField);
}
if (!isApplet()) {
final PsiClass servlet = getRefJavaManager().getServlet();
setServlet(servlet != null && psiClass.isInheritor(servlet, true));
}
if (!isApplet() && !isServlet()) {
final boolean isTestClass = TestFrameworks.getInstance().isTestClass(psiClass);
setTestCase(isTestClass);
if (isTestClass) {
for (RefClass refBase : getBaseClasses()) {
((RefClassImpl)refBase).setTestCase(true);
}
}
}
// The Android framework has a number of classes that it wants to
// instantiate so it requires these classes to be public, even if
// code analysis suggests that that these are only referenced from
// within the same package. Unfortunately these do not all extend the
// same set of base classes, so we need to check all these cases
// independently.
RefJavaManager refManager = getRefJavaManager();
if (inheritsFrom(psiClass, refManager.getAndroidActivity())
|| inheritsFrom(psiClass, refManager.getAndroidService())
|| inheritsFrom(psiClass, refManager.getAndroidView())
|| inheritsFrom(psiClass, refManager.getAndroidFragment(false))
|| inheritsFrom(psiClass, refManager.getAndroidFragment(true))
|| inheritsFrom(psiClass, refManager.getAndroidReceiver())
|| inheritsFrom(psiClass, refManager.getAndroidContentProvider())
|| inheritsFrom(psiClass, refManager.getAndroidParcelable())
|| inheritsFrom(psiClass, refManager.getAndroidBackupAgent())
|| inheritsFrom(psiClass, refManager.getAndroidActionProvider())) {
setAndroidPublic(true);
}
for (PsiMethod psiMethod : psiMethods) {
RefMethod refMethod = (RefMethod)getRefManager().getReference(psiMethod);
if (refMethod != null) {
if (psiMethod.isConstructor()) {
if (psiMethod.getParameterList().getParametersCount() > 0 || !psiMethod.hasModifierProperty(PsiModifier.PRIVATE)) {
setUtilityClass(false);
}
addConstructor(refMethod);
if (psiMethod.getParameterList().getParametersCount() == 0) {
setDefaultConstructor((RefMethodImpl)refMethod);
}
}
else {
if (!psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
setUtilityClass(false);
}
}
}
}
if (getConstructors().isEmpty() && !isInterface() && !isAnonymous()) {
RefImplicitConstructorImpl refImplicitConstructor = new RefImplicitConstructorImpl(this);
setDefaultConstructor(refImplicitConstructor);
addConstructor(refImplicitConstructor);
}
if (isInterface()) {
for (int i = 0; i < psiFields.length && isUtilityClass(); i++) {
PsiField psiField = psiFields[i];
if (!psiField.hasModifierProperty(PsiModifier.STATIC)) {
setUtilityClass(false);
}
}
}
final PsiClass applet = getRefJavaManager().getApplet();
setApplet(applet != null && psiClass.isInheritor(applet, true));
PsiManager psiManager = getRefManager().getPsiManager();
psiManager.dropResolveCaches();
PsiFile file = psiClass.getContainingFile();
if (file != null) {
InjectedLanguageManager.getInstance(file.getProject()).dropFileCaches(file);
}
}
private static boolean inheritsFrom(@NotNull PsiClass c1, @Nullable PsiClass c2) {
return c2 != null && c1.isInheritor(c2, true);
}
private static ServerPageFile getJspFile(PsiClass psiClass) {
final PsiFile psiFile = PsiUtilCore.getTemplateLanguageFile(psiClass);
return psiFile instanceof ServerPageFile ? (ServerPageFile)psiFile : null;
}
private void initializeSuperReferences(PsiClass psiClass) {
if (!isSelfInheritor(psiClass)) {
for (PsiClass psiSuperClass : psiClass.getSupers()) {
if (getRefManager().belongsToScope(psiSuperClass)) {
RefClassImpl refClass = (RefClassImpl)getRefManager().getReference(psiSuperClass);
if (refClass != null) {
addBaseClass(refClass);
refClass.addSubClass(this);
}
}
}
}
}
@Override
public boolean isSelfInheritor(PsiClass psiClass) {
return isSelfInheritor(psiClass, new ArrayList<PsiClass>());
}
@Nullable
@Override
public PsiClass getElement() {
return (PsiClass)super.getElement();
}
private static boolean isSelfInheritor(PsiClass psiClass, ArrayList<PsiClass> visited) {
if (visited.contains(psiClass)) return true;
visited.add(psiClass);
for (PsiClass aSuper : psiClass.getSupers()) {
if (isSelfInheritor(aSuper, visited)) return true;
}
visited.remove(psiClass);
return false;
}
private void setDefaultConstructor(RefMethodImpl defaultConstructor) {
if (defaultConstructor != null) {
for (RefClass superClass : getBaseClasses()) {
RefMethodImpl superDefaultConstructor = (RefMethodImpl)superClass.getDefaultConstructor();
if (superDefaultConstructor != null) {
superDefaultConstructor.addInReference(defaultConstructor);
defaultConstructor.addOutReference(superDefaultConstructor);
}
}
}
myDefaultConstructor = defaultConstructor;
}
@Override
public void buildReferences() {
PsiClass psiClass = getElement();
if (psiClass != null) {
for (PsiClassInitializer classInitializer : psiClass.getInitializers()) {
RefJavaUtil.getInstance().addReferences(psiClass, this, classInitializer.getBody());
}
RefJavaUtil.getInstance().addReferences(psiClass, this, psiClass.getModifierList());
PsiField[] psiFields = psiClass.getFields();
for (PsiField psiField : psiFields) {
getRefManager().getReference(psiField);
final PsiExpression initializer = psiField.getInitializer();
if (initializer != null) {
RefJavaUtil.getInstance().addReferences(psiClass, this, initializer);
}
}
PsiMethod[] psiMethods = psiClass.getMethods();
for (PsiMethod psiMethod : psiMethods) {
getRefManager().getReference(psiMethod);
}
RefJavaUtil.getInstance().addReferences(psiClass, this, psiClass.getExtendsList());
RefJavaUtil.getInstance().addReferences(psiClass, this, psiClass.getImplementsList());
getRefManager().fireBuildReferences(this);
}
}
@Override
public void accept(@NotNull final RefVisitor visitor) {
if (visitor instanceof RefJavaVisitor) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
((RefJavaVisitor)visitor).visitClass(RefClassImpl.this);
}
});
} else {
super.accept(visitor);
}
}
@Override
@NotNull
public Set<RefClass> getBaseClasses() {
if (myBases == null) return EMPTY_CLASS_SET;
return myBases;
}
private void addBaseClass(RefClass refClass){
if (myBases == null) {
myBases = Collections.singleton(refClass);
return;
}
if (myBases.size() == 1) {
// convert from singleton
myBases = new THashSet<RefClass>(myBases);
}
myBases.add(refClass);
}
@Override
@NotNull
public Set<RefClass> getSubClasses() {
if (mySubClasses == null) return EMPTY_CLASS_SET;
return mySubClasses;
}
private void addSubClass(@NotNull RefClass refClass){
if (mySubClasses == null) {
mySubClasses = Collections.singleton(refClass);
return;
}
if (mySubClasses.size() == 1) {
// convert from singleton
mySubClasses = new THashSet<RefClass>(mySubClasses);
}
mySubClasses.add(refClass);
}
private void removeSubClass(RefClass refClass){
if (mySubClasses == null) return;
if (mySubClasses.size() == 1) {
mySubClasses = null;
}
else {
mySubClasses.remove(refClass);
}
}
@Override
@NotNull
public List<RefMethod> getConstructors() {
if (myConstructors == null) return EMPTY_METHOD_LIST;
return myConstructors;
}
@Override
@NotNull
public Set<RefElement> getInTypeReferences() {
if (myInTypeReferences == null) return EMPTY_SET;
return myInTypeReferences;
}
public void addTypeReference(RefJavaElement from) {
if (from != null) {
if (myInTypeReferences == null){
myInTypeReferences = new THashSet<RefElement>(1);
}
myInTypeReferences.add(from);
((RefJavaElementImpl)from).addOutTypeRefernce(this);
getRefManager().fireNodeMarkedReferenced(this, from, false, false, false);
}
}
@Override
@NotNull
public Set<RefElement> getInstanceReferences() {
if (myInstanceReferences == null) return EMPTY_SET;
return myInstanceReferences;
}
public void addInstanceReference(RefElement from) {
if (myInstanceReferences == null){
myInstanceReferences = new THashSet<RefElement>(1);
}
myInstanceReferences.add(from);
}
@Override
public RefMethod getDefaultConstructor() {
return myDefaultConstructor;
}
private void addConstructor(RefMethod refConstructor) {
if (myConstructors == null){
myConstructors = new ArrayList<RefMethod>(1);
}
myConstructors.add(refConstructor);
}
public void addLibraryOverrideMethod(RefMethod refMethod) {
if (myOverridingMethods == null){
myOverridingMethods = new ArrayList<RefMethod>(2);
}
myOverridingMethods.add(refMethod);
}
@Override
@NotNull
public List<RefMethod> getLibraryMethods() {
if (myOverridingMethods == null) return EMPTY_METHOD_LIST;
return myOverridingMethods;
}
@Override
public boolean isAnonymous() {
return checkFlag(IS_ANONYMOUS_MASK);
}
@Override
public boolean isInterface() {
return checkFlag(IS_INTERFACE_MASK);
}
@Override
public boolean isSuspicious() {
return !(isUtilityClass() && getOutReferences().isEmpty()) && super.isSuspicious();
}
@Override
public boolean isUtilityClass() {
return checkFlag(IS_UTILITY_MASK);
}
@Override
public String getExternalName() {
final String[] result = new String[1];
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {//todo synthetic JSP
final PsiClass psiClass = getElement();
LOG.assertTrue(psiClass != null);
result[0] = PsiFormatUtil.getExternalName(psiClass);
}
});
return result[0];
}
@Nullable
public static RefClass classFromExternalName(RefManager manager, String externalName) {
return (RefClass) manager.getReference(ClassUtil.findPsiClass(PsiManager.getInstance(manager.getProject()), externalName));
}
@Override
public void referenceRemoved() {
super.referenceRemoved();
for (RefClass subClass : getSubClasses()) {
((RefClassImpl)subClass).removeBase(this);
}
for (RefClass superClass : getBaseClasses()) {
((RefClassImpl)superClass).removeSubClass(this);
}
}
private void removeBase(RefClass superClass) {
final Set<RefClass> baseClasses = getBaseClasses();
if (baseClasses.contains(superClass)) {
if (baseClasses.size() == 1) {
myBases = null;
return;
}
baseClasses.remove(superClass);
}
}
protected void methodRemoved(RefMethod method) {
getConstructors().remove(method);
getLibraryMethods().remove(method);
if (getDefaultConstructor() == method) {
setDefaultConstructor(null);
}
}
@Override
public boolean isAbstract() {
return checkFlag(IS_ABSTRACT_MASK);
}
@Override
public boolean isApplet() {
return checkFlag(IS_APPLET_MASK);
}
@Override
public boolean isServlet() {
return checkFlag(IS_SERVLET_MASK);
}
@Override
public boolean isAndroidPublic() {
return checkFlag(IS_ANDROID_MASK);
}
@Override
public boolean isTestCase() {
return checkFlag(IS_TESTCASE_MASK);
}
@Override
public boolean isLocalClass() {
return checkFlag(IS_LOCAL_MASK);
}
@Override
public boolean isReferenced() {
if (super.isReferenced()) return true;
if (isInterface() || isAbstract()) {
if (!getSubClasses().isEmpty()) return true;
}
return false;
}
@Override
public boolean hasSuspiciousCallers() {
if (super.hasSuspiciousCallers()) return true;
if (isInterface() || isAbstract()) {
if (!getSubClasses().isEmpty()) return true;
}
return false;
}
public void addClassExporter(RefJavaElement exporter) {
if (myClassExporters == null) myClassExporters = new ArrayList<RefJavaElement>(1);
if (myClassExporters.contains(exporter)) return;
myClassExporters.add(exporter);
}
public List<RefJavaElement> getClassExporters() {
return myClassExporters;
}
private void setAnonymous(boolean anonymous) {
setFlag(anonymous, IS_ANONYMOUS_MASK);
}
private void setInterface(boolean anInterface) {
setFlag(anInterface, IS_INTERFACE_MASK);
}
private void setUtilityClass(boolean utilityClass) {
setFlag(utilityClass, IS_UTILITY_MASK);
}
private void setAbstract(boolean anAbstract) {
setFlag(anAbstract, IS_ABSTRACT_MASK);
}
private void setApplet(boolean applet) {
setFlag(applet, IS_APPLET_MASK);
}
private void setServlet(boolean servlet) {
setFlag(servlet, IS_SERVLET_MASK);
}
private void setAndroidPublic(boolean android) {
setFlag(android, IS_ANDROID_MASK);
}
private void setTestCase(boolean testCase) {
setFlag(testCase, IS_TESTCASE_MASK);
}
private void setIsLocal(boolean isLocal) {
setFlag(isLocal, IS_LOCAL_MASK);
}
@Override
@NotNull
public RefElement getContainingEntry() {
RefElement defaultConstructor = getDefaultConstructor();
if (defaultConstructor != null) return defaultConstructor;
return super.getContainingEntry();
}
}