accept that with different library versions inheritance relation may be intransitive...
[idea/community.git] / java / java-impl / src / com / intellij / util / xml / impl / ExtendsClassChecker.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.util.xml.impl;
17
18 import com.intellij.openapi.project.Project;
19 import com.intellij.psi.*;
20 import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference;
21 import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider;
22 import com.intellij.psi.util.InheritanceUtil;
23 import com.intellij.psi.util.PsiUtil;
24 import com.intellij.util.ProcessingContext;
25 import com.intellij.util.ReflectionCache;
26 import com.intellij.util.SmartList;
27 import com.intellij.util.xml.*;
28 import com.intellij.util.xml.highlighting.DomCustomAnnotationChecker;
29 import com.intellij.util.xml.highlighting.DomElementAnnotationHolder;
30 import com.intellij.util.xml.highlighting.DomElementProblemDescriptor;
31 import com.intellij.util.xml.highlighting.DomHighlightingHelper;
32 import org.jetbrains.annotations.NotNull;
33
34 import java.util.Collections;
35 import java.util.List;
36
37 /**
38  * @author peter
39  */
40 public class ExtendsClassChecker extends DomCustomAnnotationChecker<ExtendClass>{
41   private static final GenericValueReferenceProvider ourProvider = new GenericValueReferenceProvider();
42
43   @NotNull
44   public Class<ExtendClass> getAnnotationClass() {
45     return ExtendClass.class;
46   }
47
48   public List<DomElementProblemDescriptor> checkForProblems(@NotNull final ExtendClass extend, @NotNull final DomElement _element, @NotNull final DomElementAnnotationHolder holder,
49                             @NotNull final DomHighlightingHelper helper) {
50     if (!(_element instanceof GenericDomValue)) return Collections.emptyList();
51     GenericDomValue element = (GenericDomValue)_element;
52
53     final Class genericValueParameter = DomUtil.getGenericValueParameter(element.getDomElementType());
54     if (genericValueParameter == null || (!ReflectionCache.isAssignable(genericValueParameter, PsiClass.class) &&
55                                            !ReflectionCache.isAssignable(genericValueParameter, PsiType.class))) {
56       return Collections.emptyList();
57     }
58
59     final Object valueObject = element.getValue();
60     PsiClass psiClass = null;
61
62     if (valueObject instanceof PsiClass) {
63       psiClass = (PsiClass)valueObject;
64     } else if (valueObject instanceof PsiClassType) {
65       psiClass = ((PsiClassType)valueObject).resolve();
66     }
67
68     if (psiClass != null) {
69         return checkExtendClass(element, psiClass, extend.value(),
70                                 extend.instantiatable(), extend.canBeDecorator(), extend.allowInterface(),
71                                 extend.allowNonPublic(), extend.allowAbstract(), extend.allowEnum(), holder);
72     }
73     return Collections.emptyList();
74   }
75
76   @NotNull
77   public static List<DomElementProblemDescriptor> checkExtendClass(final GenericDomValue element, final PsiClass value, final String name,
78                                                                    final boolean instantiatable, final boolean canBeDecorator, final boolean allowInterface,
79                                                                    final boolean allowNonPublic,
80                                                                    final boolean allowAbstract,
81                                                                    final boolean allowEnum,
82                                                                    final DomElementAnnotationHolder holder) {
83     final Project project = element.getManager().getProject();
84     PsiClass extendClass = JavaPsiFacade.getInstance(project).findClass(name, value.getResolveScope());
85     final SmartList<DomElementProblemDescriptor> list = new SmartList<DomElementProblemDescriptor>();
86     if (extendClass != null) {
87       if (!name.equals(value.getQualifiedName()) && !value.isInheritor(extendClass, true)) {
88         String message = DomBundle.message("class.is.not.a.subclass", value.getQualifiedName(), extendClass.getQualifiedName());
89         list.add(holder.createProblem(element, message));
90       }
91     }
92
93     if (instantiatable) {
94       if (value.hasModifierProperty(PsiModifier.ABSTRACT)) {
95         list.add(holder.createProblem(element, DomBundle.message("class.is.not.concrete", value.getQualifiedName())));
96       }
97       else if (!allowNonPublic && !value.hasModifierProperty(PsiModifier.PUBLIC)) {
98         list.add(holder.createProblem(element, DomBundle.message("class.is.not.public", value.getQualifiedName())));
99       }
100       else if (!PsiUtil.hasDefaultConstructor(value, true)) {
101         if (canBeDecorator) {
102           boolean hasConstructor = false;
103
104           for (PsiMethod method : value.getConstructors()) {
105             final PsiParameterList psiParameterList = method.getParameterList();
106             if (psiParameterList.getParametersCount() != 1) continue;
107             PsiTypeElement typeElement = psiParameterList.getParameters()[0].getTypeElement();
108             if (typeElement != null) {
109               final PsiType psiType = typeElement.getType();
110               if (psiType instanceof PsiClassType) {
111                 final PsiClass psiClass = ((PsiClassType)psiType).resolve();
112                 if (psiClass != null && InheritanceUtil.isInheritorOrSelf(psiClass, extendClass, true)) {
113                   hasConstructor = true;
114                   break;
115                 }
116               }
117             }
118           }
119           if (!hasConstructor) {
120             list.add(holder.createProblem(element, DomBundle.message("class.decorator.or.has.default.constructor", value.getQualifiedName())));
121           }
122         }
123         else {
124           list.add(holder.createProblem(element, DomBundle.message("class.has.no.default.constructor", value.getQualifiedName())));
125         }
126       }
127     }
128     if (!allowInterface && value.isInterface()) {
129       list.add(holder.createProblem(element, DomBundle.message("interface.not.allowed", value.getQualifiedName())));
130     }
131     if (!allowEnum && value.isEnum()) {
132       list.add(holder.createProblem(element, DomBundle.message("enum.not.allowed", value.getQualifiedName())));
133     }
134     if (!allowAbstract && value.hasModifierProperty(PsiModifier.ABSTRACT) && !value.isInterface()) {
135       list.add(holder.createProblem(element, DomBundle.message("abstract.class.not.allowed", value.getQualifiedName())));
136     }
137     return list;
138   }
139
140   public static List<DomElementProblemDescriptor> checkExtendsClassInReferences(final GenericDomValue element, final DomElementAnnotationHolder holder) {
141     final Object valueObject = element.getValue();
142     if (!(valueObject instanceof PsiClass)) return Collections.emptyList();
143
144     final PsiReference[] references = ourProvider.getReferencesByElement(DomUtil.getValueElement(element), new ProcessingContext());
145     for (PsiReference reference : references) {
146       if (reference instanceof JavaClassReference) {
147         final PsiReferenceProvider psiReferenceProvider = ((JavaClassReference)reference).getProvider();
148         final String[] value = psiReferenceProvider instanceof JavaClassReferenceProvider ? JavaClassReferenceProvider.EXTEND_CLASS_NAMES
149           .getValue(((JavaClassReferenceProvider)psiReferenceProvider).getOptions()) : null;
150         if (value != null && value.length != 0) {
151           for (String className : value) {
152             final List<DomElementProblemDescriptor> problemDescriptors =
153               checkExtendClass(element, ((PsiClass)valueObject), className, false, false, true, false, true, true, holder);
154             if (!problemDescriptors.isEmpty()) {
155               return problemDescriptors;
156             }
157           }
158         }
159       }
160     }
161     return Collections.emptyList();
162   }
163 }