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
;
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
);
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
)));
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())));
104 return Collections
.emptyList();
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
;
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
));
157 return Collections
.emptyList();
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;
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"));
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;
223 private static boolean isEmpty(final GenericDomValue child
, final String stringValue
) {
224 if (stringValue
.trim().length() != 0) {
227 if (child
instanceof GenericAttributeValue
) {
228 final XmlAttributeValue value
= ((GenericAttributeValue
)child
).getXmlAttributeValue();
229 if (value
!= null && value
.getTextRange().isEmpty()) {
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
) {
244 tagNamespace
= _tagNamespace
;
245 parentTag
= _parentTag
;
249 public String
getName() {
250 return XmlBundle
.message("insert.required.tag.fix", tagName
);
254 public String
getText() {
259 public String
getFamilyName() {
263 public boolean isAvailable(@NotNull final Project project
, final Editor editor
, final PsiFile file
) {
267 public void invoke(@NotNull final Project project
, final Editor editor
, final PsiFile file
) throws IncorrectOperationException
{
271 public boolean startInWriteAction() {
275 public void applyFix(@NotNull final Project project
, @NotNull final ProblemDescriptor descriptor
) {
279 private void doFix() {
280 if (!CodeInsightUtilBase
.prepareFileForWrite(parentTag
.getContainingFile())) return;
283 parentTag
.add(parentTag
.createChildTag(tagName
, tagNamespace
, "",false));
285 catch (IncorrectOperationException e
) {
286 throw new RuntimeException(e
);