update copyright
[fedora-idea.git] / xml / dom-impl / src / com / intellij / util / xml / highlighting / DomHighlightingHelperImpl.java
blob8f94a1e4ff21c8e452d0dbb53e62dac8608d3c1c
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.util.xml.highlighting;
18 import com.intellij.codeInsight.CodeInsightUtilBase;
19 import com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor;
20 import com.intellij.codeInsight.intention.IntentionAction;
21 import com.intellij.codeInspection.LocalQuickFix;
22 import com.intellij.codeInspection.ProblemDescriptor;
23 import com.intellij.ide.IdeBundle;
24 import com.intellij.lang.annotation.HighlightSeverity;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.psi.*;
29 import com.intellij.psi.util.PsiTreeUtil;
30 import com.intellij.psi.xml.XmlAttributeValue;
31 import com.intellij.psi.xml.XmlElement;
32 import com.intellij.psi.xml.XmlTag;
33 import com.intellij.util.IncorrectOperationException;
34 import com.intellij.util.SmartList;
35 import com.intellij.util.containers.ContainerUtil;
36 import com.intellij.util.xml.*;
37 import com.intellij.util.xml.impl.*;
38 import com.intellij.util.xml.reflect.AbstractDomChildrenDescription;
39 import com.intellij.util.xml.reflect.DomCollectionChildDescription;
40 import com.intellij.util.xml.reflect.DomGenericInfo;
41 import com.intellij.xml.XmlBundle;
42 import com.intellij.util.ProcessingContext;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
46 import java.util.Arrays;
47 import java.util.Collections;
48 import java.util.List;
50 /**
51 * @author peter
53 public class DomHighlightingHelperImpl extends DomHighlightingHelper {
54 private final GenericValueReferenceProvider myProvider = new GenericValueReferenceProvider();
55 private final DomElementAnnotationsManagerImpl myAnnotationsManager;
57 public DomHighlightingHelperImpl(final DomElementAnnotationsManagerImpl annotationsManager) {
58 myAnnotationsManager = annotationsManager;
61 public void runAnnotators(DomElement element, DomElementAnnotationHolder holder, Class<? extends DomElement> rootClass) {
62 myAnnotationsManager.annotate(element, holder, rootClass);
65 @NotNull
66 public List<DomElementProblemDescriptor> checkRequired(final DomElement element, final DomElementAnnotationHolder holder) {
67 final Required required = element.getAnnotation(Required.class);
68 if (required != null) {
69 final XmlElement xmlElement = element.getXmlElement();
70 if (xmlElement == null) {
71 if (required.value()) {
72 final String xmlElementName = element.getXmlElementName();
73 if (element instanceof GenericAttributeValue) {
74 return Arrays.asList(holder.createProblem(element, IdeBundle.message("attribute.0.should.be.defined", xmlElementName)));
76 return Arrays.asList(
77 holder.createProblem(
78 element,
79 HighlightSeverity.ERROR,
80 IdeBundle.message("child.tag.0.should.be.defined", xmlElementName),
81 new AddRequiredSubtagFix(xmlElementName, element.getXmlElementNamespace(), element.getParent().getXmlTag())
86 else if (element instanceof GenericDomValue) {
87 return ContainerUtil.createMaybeSingletonList(checkRequiredGenericValue((GenericDomValue)element, required, holder));
90 if (DomUtil.hasXml(element)) {
91 final SmartList<DomElementProblemDescriptor> list = new SmartList<DomElementProblemDescriptor>();
92 final DomGenericInfo info = element.getGenericInfo();
93 for (final AbstractDomChildrenDescription description : info.getChildrenDescriptions()) {
94 if (description instanceof DomCollectionChildDescription && description.getValues(element).isEmpty()) {
95 final DomCollectionChildDescription childDescription = (DomCollectionChildDescription)description;
96 final Required annotation = description.getAnnotation(Required.class);
97 if (annotation != null && annotation.value()) {
98 list.add(holder.createProblem(element, childDescription, IdeBundle.message("child.tag.0.should.be.defined", ((DomCollectionChildDescription)description).getXmlElementName())));
102 return list;
104 return Collections.emptyList();
107 @NotNull
108 public List<DomElementProblemDescriptor> checkResolveProblems(GenericDomValue element, final DomElementAnnotationHolder holder) {
109 if (StringUtil.isEmpty(element.getStringValue())) {
110 final Required required = element.getAnnotation(Required.class);
111 if (required != null && !required.nonEmpty()) return Collections.emptyList();
114 final XmlElement valueElement = DomUtil.getValueElement(element);
115 if (valueElement != null && !isSoftReference(element)) {
116 final SmartList<DomElementProblemDescriptor> list = new SmartList<DomElementProblemDescriptor>();
117 final PsiReference[] psiReferences = myProvider.getReferencesByElement(valueElement, new ProcessingContext());
118 GenericDomValueReference domReference = null;
119 for (final PsiReference reference : psiReferences) {
120 if (reference instanceof GenericDomValueReference) {
121 domReference = (GenericDomValueReference)reference;
122 break;
125 final Converter converter = WrappingConverter.getDeepestConverter(element.getConverter(), element);
126 final boolean domReferenceResolveOK = domReference != null && !hasBadResolve(element, domReference)
127 || domReference != null && converter instanceof ResolvingConverter && ((ResolvingConverter)converter).getAdditionalVariants(domReference.getConvertContext()).contains(element.getStringValue());
128 boolean hasBadResolve = false;
129 if (!domReferenceResolveOK) {
130 for (final PsiReference reference : psiReferences) {
131 if (reference != domReference && hasBadResolve(element, reference)) {
132 hasBadResolve = true;
133 list.add(holder.createResolveProblem(element, reference));
136 final boolean isResolvingConverter = converter instanceof ResolvingConverter;
137 if (!hasBadResolve &&
138 (domReference != null || isResolvingConverter &&
139 hasBadResolve(element, domReference = new GenericDomValueReference(element)))) {
140 hasBadResolve = true;
141 final String errorMessage = converter
142 .getErrorMessage(element.getStringValue(), new ConvertContextImpl(DomManagerImpl.getDomInvocationHandler(element)));
143 if (errorMessage != null && XmlHighlightVisitor.getErrorDescription(domReference) != null) {
144 list.add(holder.createResolveProblem(element, domReference));
148 if (!hasBadResolve && psiReferences.length == 0 && element.getValue() == null && !PsiTreeUtil.hasErrorElements(valueElement)) {
149 final String errorMessage = converter
150 .getErrorMessage(element.getStringValue(), new ConvertContextImpl(DomManagerImpl.getDomInvocationHandler(element)));
151 if (errorMessage != null) {
152 list.add(holder.createProblem(element, errorMessage));
155 return list;
157 return Collections.emptyList();
160 @NotNull
161 public List<DomElementProblemDescriptor> checkNameIdentity(DomElement element, final DomElementAnnotationHolder holder) {
162 final String elementName = ElementPresentationManager.getElementName(element);
163 if (StringUtil.isNotEmpty(elementName)) {
164 final DomElement domElement = DomUtil.findDuplicateNamedValue(element, elementName);
165 if (domElement != null) {
166 final String typeName = ElementPresentationManager.getTypeNameForObject(element);
167 final GenericDomValue genericDomValue = domElement.getGenericInfo().getNameDomElement(element);
168 if (genericDomValue != null) {
169 return Arrays.asList(holder.createProblem(genericDomValue, DomUtil.getFile(domElement).equals(DomUtil.getFile(element))
170 ? IdeBundle.message("model.highlighting.identity", typeName)
171 : IdeBundle.message("model.highlighting.identity.in.other.file", typeName,
172 domElement.getXmlTag().getContainingFile().getName())));
176 return Collections.emptyList();
179 private static boolean hasBadResolve(GenericDomValue value, PsiReference reference) {
180 return XmlHighlightVisitor.hasBadResolve(reference);
183 private static boolean isSoftReference(GenericDomValue value) {
184 final Resolve resolve = value.getAnnotation(Resolve.class);
185 if (resolve != null && resolve.soft()) return true;
187 final Convert convert = value.getAnnotation(Convert.class);
188 if (convert != null && convert.soft()) return true;
190 final Referencing referencing = value.getAnnotation(Referencing.class);
191 if (referencing != null && referencing.soft()) return true;
193 return false;
196 @Nullable
197 private static DomElementProblemDescriptor checkRequiredGenericValue(final GenericDomValue child, final Required required,
198 final DomElementAnnotationHolder annotator) {
199 final String stringValue = child.getStringValue();
200 if (stringValue == null) return null;
202 if (required.nonEmpty() && isEmpty(child, stringValue)) {
203 return annotator.createProblem(child, IdeBundle.message("value.must.not.be.empty"));
205 if (required.identifier() && !isIdentifier(stringValue)) {
206 return annotator.createProblem(child, IdeBundle.message("value.must.be.identifier"));
208 return null;
211 private static boolean isIdentifier(final String s) {
212 if (StringUtil.isEmptyOrSpaces(s)) return false;
214 if (!Character.isJavaIdentifierStart(s.charAt(0))) return false;
216 for (int i = 1; i < s.length(); i++) {
217 if (!Character.isJavaIdentifierPart(s.charAt(i))) return false;
220 return true;
223 private static boolean isEmpty(final GenericDomValue child, final String stringValue) {
224 if (stringValue.trim().length() != 0) {
225 return false;
227 if (child instanceof GenericAttributeValue) {
228 final XmlAttributeValue value = ((GenericAttributeValue)child).getXmlAttributeValue();
229 if (value != null && value.getTextRange().isEmpty()) {
230 return false;
233 return true;
237 private static class AddRequiredSubtagFix implements LocalQuickFix, IntentionAction {
238 private final String tagName;
239 private final String tagNamespace;
240 private final XmlTag parentTag;
242 public AddRequiredSubtagFix(@NotNull String _tagName, @NotNull String _tagNamespace, @NotNull XmlTag _parentTag) {
243 tagName = _tagName;
244 tagNamespace = _tagNamespace;
245 parentTag = _parentTag;
248 @NotNull
249 public String getName() {
250 return XmlBundle.message("insert.required.tag.fix", tagName);
253 @NotNull
254 public String getText() {
255 return getName();
258 @NotNull
259 public String getFamilyName() {
260 return getName();
263 public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) {
264 return true;
267 public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
268 doFix();
271 public boolean startInWriteAction() {
272 return true;
275 public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
276 doFix();
279 private void doFix() {
280 if (!CodeInsightUtilBase.prepareFileForWrite(parentTag.getContainingFile())) return;
282 try {
283 parentTag.add(parentTag.createChildTag(tagName, tagNamespace, "",false));
285 catch (IncorrectOperationException e) {
286 throw new RuntimeException(e);