IDEADEV-15617 && IDEADEV-15613
[fedora-idea.git] / dom / impl / src / com / intellij / util / xml / highlighting / DomHighlightingHelperImpl.java
blob17abd2abec35e2b78bdccf97f3d1592bd94bcc87
1 /*
2 * Copyright (c) 2000-2005 by JetBrains s.r.o. All Rights Reserved.
3 * Use is subject to license terms.
4 */
5 package com.intellij.util.xml.highlighting;
7 import com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor;
8 import com.intellij.ide.IdeBundle;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.util.text.StringUtil;
11 import com.intellij.psi.*;
12 import com.intellij.psi.search.GlobalSearchScope;
13 import com.intellij.psi.util.InheritanceUtil;
14 import com.intellij.psi.xml.XmlAttributeValue;
15 import com.intellij.psi.xml.XmlElement;
16 import com.intellij.util.SmartList;
17 import com.intellij.util.containers.ContainerUtil;
18 import com.intellij.util.xml.*;
19 import com.intellij.util.xml.impl.ConvertContextImpl;
20 import com.intellij.util.xml.impl.DomManagerImpl;
21 import com.intellij.util.xml.impl.GenericDomValueReference;
22 import com.intellij.util.xml.impl.GenericValueReferenceProvider;
23 import com.intellij.util.xml.reflect.DomChildrenDescription;
24 import com.intellij.util.xml.reflect.DomCollectionChildDescription;
25 import com.intellij.util.xml.reflect.DomGenericInfo;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Set;
34 /**
35 * @author peter
37 public class DomHighlightingHelperImpl extends DomHighlightingHelper {
38 private final GenericValueReferenceProvider myProvider = new GenericValueReferenceProvider();
39 private final DomElementAnnotationsManagerImpl myAnnotationsManager;
41 public DomHighlightingHelperImpl(final DomElementAnnotationsManagerImpl annotationsManager) {
42 myAnnotationsManager = annotationsManager;
45 public void runAnnotators(DomElement element, DomElementAnnotationHolder holder, Class<? extends DomElement> rootClass) {
46 myAnnotationsManager.annotate(element, holder, rootClass);
49 @NotNull
50 public List<DomElementProblemDescriptor> checkRequired(DomElement element, final DomElementAnnotationHolder holder) {
51 final Required required = element.getAnnotation(Required.class);
52 if (required != null) {
53 final XmlElement xmlElement = element.getXmlElement();
54 if (xmlElement == null) {
55 if (required.value()) {
56 if (element instanceof GenericAttributeValue) {
57 return Arrays.asList(holder.createProblem(element, IdeBundle.message("attribute.0.should.be.defined", element.getXmlElementName())));
59 return Arrays.asList(holder.createProblem(element, IdeBundle.message("child.tag.0.should.be.defined", element.getXmlElementName())));
62 else if (element instanceof GenericDomValue) {
63 return ContainerUtil.createMaybeSingletonList(checkRequiredGenericValue((GenericDomValue)element, required, holder));
66 if (element.getXmlElement() != null) {
67 final SmartList<DomElementProblemDescriptor> list = new SmartList<DomElementProblemDescriptor>();
68 final DomGenericInfo info = element.getGenericInfo();
69 for (final DomChildrenDescription description : info.getChildrenDescriptions()) {
70 if (description instanceof DomCollectionChildDescription && description.getValues(element).isEmpty()) {
71 final DomCollectionChildDescription childDescription = (DomCollectionChildDescription)description;
72 final Required annotation = description.getAnnotation(Required.class);
73 if (annotation != null && annotation.value()) {
74 list.add(holder.createProblem(element, childDescription,
75 IdeBundle.message("child.tag.0.should.be.defined", description.getXmlElementName())));
79 return list;
81 return Collections.emptyList();
84 @NotNull
85 public List<DomElementProblemDescriptor> checkExtendClass(GenericDomValue element, final DomElementAnnotationHolder holder) {
86 final Class genericValueParameter = DomUtil.getGenericValueParameter(element.getDomElementType());
87 if (genericValueParameter == null || !PsiClass.class.isAssignableFrom(genericValueParameter)) {
88 return Collections.emptyList();
91 PsiClass value = (PsiClass)element.getValue();
92 if (value != null) {
93 ExtendClass extend = element.getAnnotation(ExtendClass.class);
94 if (extend != null) {
95 Project project = element.getManager().getProject();
96 final String name = extend.value();
97 PsiClass extendClass = PsiManager.getInstance(project).findClass(name, GlobalSearchScope.allScope(project));
98 if (extendClass != null) {
99 final SmartList<DomElementProblemDescriptor> list = new SmartList<DomElementProblemDescriptor>();
100 if (!name.equals(value.getQualifiedName()) && !value.isInheritor(extendClass, true)) {
101 String message = IdeBundle.message("class.is.not.a.subclass", value.getQualifiedName(), extendClass.getQualifiedName());
102 list.add(holder.createProblem(element, message));
103 } else if (extend.instantiatable()) {
104 if (value.hasModifierProperty(PsiModifier.ABSTRACT)) {
105 list.add(holder.createProblem(element, IdeBundle.message("class.is.not.concrete", value.getQualifiedName())));
106 } else if (!value.hasModifierProperty(PsiModifier.PUBLIC)) {
107 list.add(holder.createProblem(element, IdeBundle.message("class.is.not.public", value.getQualifiedName())));
108 } else if (!hasDefaultConstructor(value)) {
109 if (extend.canBeDecorator()) {
110 boolean hasConstructor = false;
112 for (PsiMethod method : value.getConstructors()) {
113 final PsiParameterList psiParameterList = method.getParameterList();
114 if (psiParameterList.getParametersCount() != 1) continue;
115 final PsiType psiType = psiParameterList.getParameters()[0].getTypeElement().getType();
116 if (psiType instanceof PsiClassType) {
117 final PsiClass psiClass = ((PsiClassType)psiType).resolve();
118 if (psiClass != null && InheritanceUtil.isInheritorOrSelf(psiClass, extendClass, true)) {
119 hasConstructor = true;
120 break;
123 } if (!hasConstructor) {
124 list.add(holder.createProblem(element, IdeBundle.message("class.decorator.or.has.default.constructor", value.getQualifiedName())));
126 } else {
127 list.add(holder.createProblem(element, IdeBundle.message("class.has.no.default.constructor", value.getQualifiedName())));
131 return list;
135 return Collections.emptyList();
138 @NotNull
139 public List<DomElementProblemDescriptor> checkResolveProblems(GenericDomValue element, final DomElementAnnotationHolder holder) {
140 final XmlElement valueElement = DomUtil.getValueElement(element);
141 if (valueElement != null && !isSoftReference(element)) {
142 final SmartList<DomElementProblemDescriptor> list = new SmartList<DomElementProblemDescriptor>();
143 final PsiReference[] psiReferences = myProvider.getReferencesByElement(valueElement);
144 boolean hasBadResolve = false;
145 boolean hasDomValueReference = false;
146 for (final PsiReference reference : psiReferences) {
147 hasDomValueReference = hasDomValueReference || reference instanceof GenericDomValueReference;
148 if (hasBadResolve(element, reference)) {
149 hasBadResolve = true;
150 list.add(holder.createResolveProblem(element, reference));
153 if (!hasBadResolve && !hasDomValueReference && element.getConverter() instanceof ResolvingConverter) {
154 final GenericDomValueReference reference = new GenericDomValueReference(element);
155 if (hasBadResolve(element, reference)) {
156 list.add(holder.createResolveProblem(element, reference));
159 if (psiReferences.length == 0 && element.getValue() == null) {
160 final String errorMessage = element.getConverter()
161 .getErrorMessage(element.getStringValue(), new ConvertContextImpl(DomManagerImpl.getDomInvocationHandler(element)));
162 list.add(holder.createProblem(element, errorMessage));
164 return list;
166 return Collections.emptyList();
169 @NotNull
170 public List<DomElementProblemDescriptor> checkNameIdentity(DomElement element, final DomElementAnnotationHolder holder) {
171 final String elementName = ElementPresentationManager.getElementName(element);
172 if (StringUtil.isNotEmpty(elementName)) {
173 final DomElement domElement = DomUtil.findDuplicateNamedValue(element, elementName);
174 if (domElement != null) {
175 final String typeName = ElementPresentationManager.getTypeNameForObject(element);
176 final GenericDomValue genericDomValue = domElement.getGenericInfo().getNameDomElement(element);
177 return Arrays.asList(holder.createProblem(genericDomValue, domElement.getRoot().equals(element.getRoot())
178 ? IdeBundle.message("model.highlighting.identity", typeName)
179 : IdeBundle.message("model.highlighting.identity.in.other.file", typeName,
180 domElement.getXmlTag().getContainingFile().getName())));
183 return Collections.emptyList();
186 private static boolean hasBadResolve(GenericDomValue value, PsiReference reference) {
187 if (XmlHighlightVisitor.hasBadResolve(reference)) {
188 final Converter converter = value.getConverter();
189 if (converter instanceof ResolvingConverter) {
190 final ResolvingConverter resolvingConverter = (ResolvingConverter)converter;
191 final Set additionalVariants = resolvingConverter.getAdditionalVariants();
192 if (additionalVariants.contains(value.getStringValue())) {
193 return false;
196 return true;
198 return false;
201 private static boolean isSoftReference(GenericDomValue value) {
202 final Resolve resolve = value.getAnnotation(Resolve.class);
203 if (resolve != null && resolve.soft()) return true;
205 final Convert convert = value.getAnnotation(Convert.class);
206 if (convert != null && convert.soft()) return true;
208 final Referencing referencing = value.getAnnotation(Referencing.class);
209 if (referencing != null && referencing.soft()) return true;
211 return false;
214 @Nullable
215 private static DomElementProblemDescriptor checkRequiredGenericValue(final GenericDomValue child, final Required required,
216 final DomElementAnnotationHolder annotator) {
217 assert child.getXmlTag() != null;
219 final String stringValue = child.getStringValue();
220 assert stringValue != null;
221 if (required.nonEmpty() && isEmpty(child, stringValue)) {
222 return annotator.createProblem(child, IdeBundle.message("value.must.not.be.empty"));
224 if (required.identifier() && !PsiManager.getInstance(child.getManager().getProject()).getNameHelper().isIdentifier(stringValue)) {
225 return annotator.createProblem(child, IdeBundle.message("value.must.be.identifier"));
227 return null;
230 private static boolean isEmpty(final GenericDomValue child, final String stringValue) {
231 if (stringValue.trim().length() != 0) {
232 return false;
234 if (child instanceof GenericAttributeValue) {
235 final XmlAttributeValue value = ((GenericAttributeValue)child).getXmlAttributeValue();
236 if (value != null && value.getTextRange().isEmpty()) {
237 return false;
240 return true;
244 private static boolean hasDefaultConstructor(PsiClass clazz) {
245 final PsiMethod[] constructors = clazz.getConstructors();
246 if (constructors.length > 0) {
247 for (PsiMethod cls: constructors) {
248 if ((cls.hasModifierProperty(PsiModifier.PUBLIC) || cls.hasModifierProperty(PsiModifier.PROTECTED)) && cls.getParameterList().getParametersCount() == 0) {
249 return true;
252 } else {
253 final PsiClass superClass = clazz.getSuperClass();
254 return superClass == null || hasDefaultConstructor(superClass);
256 return false;