update copyright
[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.search.GlobalSearchScope;
23 import com.intellij.psi.util.InheritanceUtil;
24 import com.intellij.util.ReflectionCache;
25 import com.intellij.util.SmartList;
26 import com.intellij.util.ProcessingContext;
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, GlobalSearchScope.allScope(project));
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 (!hasDefaultConstructor(value)) {
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             final PsiType psiType = psiParameterList.getParameters()[0].getTypeElement().getType();
108             if (psiType instanceof PsiClassType) {
109               final PsiClass psiClass = ((PsiClassType)psiType).resolve();
110               if (psiClass != null && InheritanceUtil.isInheritorOrSelf(psiClass, extendClass, true)) {
111                 hasConstructor = true;
112                 break;
113               }
114             }
115           }
116           if (!hasConstructor) {
117             list.add(holder.createProblem(element, DomBundle.message("class.decorator.or.has.default.constructor", value.getQualifiedName())));
118           }
119         }
120         else {
121           list.add(holder.createProblem(element, DomBundle.message("class.has.no.default.constructor", value.getQualifiedName())));
122         }
123       }
124     }
125     if (!allowInterface && value.isInterface()) {
126       list.add(holder.createProblem(element, DomBundle.message("interface.not.allowed", value.getQualifiedName())));
127     }
128     if (!allowEnum && value.isEnum()) {
129       list.add(holder.createProblem(element, DomBundle.message("enum.not.allowed", value.getQualifiedName())));
130     }
131     if (!allowAbstract && value.hasModifierProperty(PsiModifier.ABSTRACT) && !value.isInterface()) {
132       list.add(holder.createProblem(element, DomBundle.message("abstract.class.not.allowed", value.getQualifiedName())));
133     }
134     return list;
135   }
136
137   public static boolean hasDefaultConstructor(PsiClass clazz) {
138     final PsiMethod[] constructors = clazz.getConstructors();
139     if (constructors.length > 0) {
140       for (PsiMethod cls: constructors) {
141         if ((cls.hasModifierProperty(PsiModifier.PUBLIC) || cls.hasModifierProperty(PsiModifier.PROTECTED)) && cls.getParameterList().getParametersCount() == 0) {
142           return true;
143         }
144       }
145     } else {
146       final PsiClass superClass = clazz.getSuperClass();
147       return superClass == null || hasDefaultConstructor(superClass);
148     }
149     return false;
150   }
151
152   public static List<DomElementProblemDescriptor> checkExtendsClassInReferences(final GenericDomValue element, final DomElementAnnotationHolder holder) {
153     final Object valueObject = element.getValue();
154     if (!(valueObject instanceof PsiClass)) return Collections.emptyList();
155
156     final PsiReference[] references = ourProvider.getReferencesByElement(DomUtil.getValueElement(element), new ProcessingContext());
157     for (PsiReference reference : references) {
158       if (reference instanceof JavaClassReference) {
159         final PsiReferenceProvider psiReferenceProvider = ((JavaClassReference)reference).getProvider();
160         final String[] value = psiReferenceProvider instanceof JavaClassReferenceProvider ? JavaClassReferenceProvider.EXTEND_CLASS_NAMES
161           .getValue(((JavaClassReferenceProvider)psiReferenceProvider).getOptions()) : null;
162         if (value != null && value.length != 0) {
163           for (String className : value) {
164             final List<DomElementProblemDescriptor> problemDescriptors =
165               checkExtendClass(element, ((PsiClass)valueObject), className, false, false, true, false, true, true, holder);
166             if (!problemDescriptors.isEmpty()) {
167               return problemDescriptors;
168             }
169           }
170         }
171       }
172     }
173     return Collections.emptyList();
174   }
175 }