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
.html
;
18 import com
.intellij
.lang
.Language
;
19 import com
.intellij
.lang
.html
.HTMLLanguage
;
20 import com
.intellij
.lang
.xhtml
.XHTMLLanguage
;
21 import com
.intellij
.lang
.xml
.XMLLanguage
;
22 import com
.intellij
.lexer
.FilterLexer
;
23 import com
.intellij
.lexer
.HtmlHighlightingLexer
;
24 import com
.intellij
.lexer
.Lexer
;
25 import com
.intellij
.lexer
.XHtmlHighlightingLexer
;
26 import com
.intellij
.openapi
.fileTypes
.FileType
;
27 import com
.intellij
.openapi
.fileTypes
.LanguageFileType
;
28 import com
.intellij
.openapi
.project
.Project
;
29 import com
.intellij
.openapi
.util
.text
.StringUtil
;
30 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
31 import com
.intellij
.openapi
.vfs
.VirtualFile
;
32 import com
.intellij
.psi
.*;
33 import com
.intellij
.psi
.impl
.source
.resolve
.reference
.impl
.providers
.FileReference
;
34 import com
.intellij
.psi
.impl
.source
.resolve
.reference
.impl
.providers
.FileReferenceSet
;
35 import com
.intellij
.psi
.impl
.source
.tree
.Factory
;
36 import com
.intellij
.psi
.impl
.source
.tree
.LeafElement
;
37 import com
.intellij
.psi
.search
.GlobalSearchScope
;
38 import com
.intellij
.psi
.templateLanguages
.OuterLanguageElement
;
39 import com
.intellij
.psi
.templateLanguages
.TemplateLanguage
;
40 import com
.intellij
.psi
.templateLanguages
.TemplateLanguageFileViewProvider
;
41 import com
.intellij
.psi
.tree
.IElementType
;
42 import com
.intellij
.psi
.util
.PsiTreeUtil
;
43 import com
.intellij
.psi
.xml
.*;
44 import com
.intellij
.util
.indexing
.*;
45 import com
.intellij
.util
.io
.DataExternalizer
;
46 import org
.jetbrains
.annotations
.NonNls
;
47 import org
.jetbrains
.annotations
.NotNull
;
48 import org
.jetbrains
.annotations
.Nullable
;
50 import java
.io
.DataInput
;
51 import java
.io
.DataOutput
;
52 import java
.io
.IOException
;
53 import java
.util
.ArrayList
;
54 import java
.util
.Arrays
;
55 import java
.util
.List
;
60 public class HtmlLinkTagIndex
extends SingleEntryFileBasedIndexExtension
<HtmlLinkTagIndex
.InfoHolder
<HtmlLinkTagIndex
.LinkInfo
>> {
61 public static final ID
<Integer
, InfoHolder
<LinkInfo
>> INDEX_ID
= ID
.create("HtmlLinkTagIndex");
63 @NonNls private static final String LINK
= "link";
64 @NonNls private static final String HREF_ATTR
= "href";
65 @NonNls private static final String MEDIA_ATTR
= "media";
66 @NonNls private static final String REL_ATTR
= "rel";
67 @NonNls private static final String TITLE_ATTR
= "title";
68 @NonNls private static final String TYPE_ATTR
= "type";
70 private final FileBasedIndex
.InputFilter myInputFilter
= new FileBasedIndex
.InputFilter() {
71 public boolean acceptInput(final VirtualFile file
) {
72 if (!(file
.getFileSystem() instanceof LocalFileSystem
)) {
76 final FileType fileType
= file
.getFileType();
77 if (!(fileType
instanceof LanguageFileType
)) {
81 final LanguageFileType languageFileType
= (LanguageFileType
)fileType
;
82 final Language language
= languageFileType
.getLanguage();
84 return language
instanceof TemplateLanguage
|| (language
instanceof XMLLanguage
&& language
!= XMLLanguage
.INSTANCE
);
87 private final DataExternalizer
<InfoHolder
<LinkInfo
>> myValueExternalizer
= new DataExternalizer
<InfoHolder
<LinkInfo
>>() {
88 public void save(DataOutput out
, InfoHolder
<LinkInfo
> value
) throws IOException
{
89 out
.writeInt(value
.myValues
.length
);
90 for (final LinkInfo linkInfo
: value
.myValues
) {
91 out
.writeInt(linkInfo
.offset
);
92 out
.writeBoolean(linkInfo
.scripted
);
94 if (!linkInfo
.scripted
) {
95 writeString(linkInfo
.value
, out
);
98 writeString(linkInfo
.media
, out
);
99 writeString(linkInfo
.type
, out
);
100 writeString(linkInfo
.rel
, out
);
101 writeString(linkInfo
.title
, out
);
105 private void writeString(String s
, DataOutput out
) throws IOException
{
106 out
.writeBoolean(s
!= null);
112 public InfoHolder
<LinkInfo
> read(DataInput in
) throws IOException
{
113 final int size
= in
.readInt();
114 final List
<LinkInfo
> list
= new ArrayList
<LinkInfo
>(size
);
115 for (int i
= 0; i
< size
; i
++) {
116 final int offset
= in
.readInt();
117 final boolean scripted
= in
.readBoolean();
119 final String href
= !scripted ?
(in
.readBoolean() ? in
.readUTF() : null) : null;
120 final String media
= in
.readBoolean() ? in
.readUTF() : null;
121 final String type
= in
.readBoolean() ? in
.readUTF() : null;
122 final String rel
= in
.readBoolean() ? in
.readUTF() : null;
123 final String title
= in
.readBoolean() ? in
.readUTF() : null;
125 list
.add(new LinkInfo(offset
, scripted
, href
, media
, type
, rel
, title
));
128 return new InfoHolder
<LinkInfo
>(list
.toArray(new LinkInfo
[list
.size()]));
132 public ID
<Integer
, InfoHolder
<LinkInfo
>> getName() {
136 public interface LinkReferenceResult
{
138 PsiFile
getReferencedFile();
143 boolean isScriptedReference();
146 String
getMediaValue();
149 String
getRelValue();
152 String
getTitleValue();
156 public static List
<LinkReferenceResult
> getReferencedFiles(@NotNull final VirtualFile _file
, @NotNull final Project project
) {
157 final List
<LinkReferenceResult
> result
= new ArrayList
<LinkReferenceResult
>();
158 if (!(_file
.getFileSystem() instanceof LocalFileSystem
)) {
162 FileBasedIndex
.getInstance()
163 .processValues(INDEX_ID
, FileBasedIndex
.getFileId(_file
), null, new FileBasedIndex
.ValueProcessor
<InfoHolder
<LinkInfo
>>() {
164 public boolean process(final VirtualFile file
, final InfoHolder
<LinkInfo
> value
) {
165 final PsiManager psiManager
= PsiManager
.getInstance(project
);
166 final PsiFile psiFile
= psiManager
.findFile(file
);
167 if (psiFile
!= null) {
168 for (final LinkInfo linkInfo
: value
.myValues
) {
169 if (linkInfo
.value
!= null || linkInfo
.scripted
) {
170 final PsiFileSystemItem
[] item
= new PsiFileSystemItem
[]{null};
171 if (linkInfo
.value
!= null) {
172 final LeafElement newValueElement
= Factory
173 .createSingleLeafElement(XmlTokenType
.XML_ATTRIBUTE_VALUE_TOKEN
, "\"" + linkInfo
.value
+ "\"", 0,
174 linkInfo
.value
.length() + 2, null, psiManager
, psiFile
);
175 final PsiElement element
= newValueElement
.getPsi();
176 final FileReferenceSet set
=
177 new FileReferenceSet(StringUtil
.stripQuotesAroundValue(element
.getText()), element
, 1, null, true);
179 final FileReference lastReference
= set
.getLastReference();
181 if (lastReference
!= null) {
182 final PsiFileSystemItem resolved
= lastReference
.resolve();
183 if (resolved
instanceof PsiFile
) {
189 result
.add(new MyLinkReferenceResult(item
, linkInfo
, psiFile
));
195 }, GlobalSearchScope
.allScope(project
));
200 public SingleEntryIndexer
<InfoHolder
<LinkInfo
>> getIndexer() {
201 return new SingleEntryIndexer
<InfoHolder
<LinkInfo
>>(false) {
202 protected InfoHolder
<LinkInfo
> computeValue(@NotNull FileContent inputData
) {
203 final Language language
= ((LanguageFileType
)inputData
.getFileType()).getLanguage();
205 final List
<LinkInfo
> result
= new ArrayList
<LinkInfo
>();
207 if (HTMLLanguage
.INSTANCE
== language
|| XHTMLLanguage
.INSTANCE
== language
) {
208 mapHtml(inputData
, language
, result
);
211 mapJsp(inputData
, result
);
214 return new InfoHolder
<LinkInfo
>(result
.toArray(new LinkInfo
[result
.size()]));
219 private static void mapJsp(FileContent inputData
, final List
<LinkInfo
> result
) {
220 final FileViewProvider viewProvider
= inputData
.getPsiFile().getViewProvider();
222 PsiFile psiFile
= null;
223 if (viewProvider
instanceof TemplateLanguageFileViewProvider
) {
224 final Language dataLanguage
= ((TemplateLanguageFileViewProvider
)viewProvider
).getTemplateDataLanguage();
225 if (dataLanguage
== HTMLLanguage
.INSTANCE
|| dataLanguage
== XHTMLLanguage
.INSTANCE
) {
226 psiFile
= viewProvider
.getPsi(dataLanguage
);
230 psiFile
= viewProvider
.getPsi(viewProvider
.getBaseLanguage());
233 if (psiFile
!= null) {
234 final XmlRecursiveElementVisitor visitor
= new XmlRecursiveElementVisitor() {
236 public void visitXmlTag(XmlTag tag
) {
237 if (LINK
.equalsIgnoreCase(tag
.getLocalName())) {
239 final String href
= getAttributeValue(tag
, HREF_ATTR
);
240 final String media
= getAttributeValue(tag
, MEDIA_ATTR
);
241 final String type
= getAttributeValue(tag
, TYPE_ATTR
);
242 final String rel
= getAttributeValue(tag
, REL_ATTR
);
243 final String title
= getAttributeValue(tag
, TITLE_ATTR
);
245 addResult(result
, tag
.getTextOffset(), href
, media
, type
, rel
, title
, isHrefScripted(tag
));
248 super.visitXmlTag(tag
);
252 psiFile
.accept(visitor
);
256 private static void mapHtml(FileContent inputData
, Language language
, List
<LinkInfo
> result
) {
257 final Lexer original
= HTMLLanguage
.INSTANCE
== language ?
new HtmlHighlightingLexer() : new XHtmlHighlightingLexer();
258 final Lexer lexer
= new FilterLexer(original
, new FilterLexer
.Filter() {
259 public boolean reject(final IElementType type
) {
260 return XmlElementType
.XML_WHITE_SPACE
== type
;
264 final CharSequence data
= inputData
.getContentAsText();
267 IElementType tokenType
= lexer
.getTokenType();
268 boolean linkTag
= false;
269 while (tokenType
!= null) {
270 if (XmlElementType
.XML_TAG_NAME
== tokenType
) {
271 final String tagName
= data
.subSequence(lexer
.getTokenStart(), lexer
.getTokenEnd()).toString();
272 linkTag
= LINK
.equalsIgnoreCase(tagName
);
273 //if (BODY_TAG.equalsIgnoreCase(tagName)) {
274 // break; // there are no LINK tags under the body
278 if (linkTag
&& XmlElementType
.XML_NAME
== tokenType
) {
279 int linkTagOffset
= lexer
.getTokenStart();
287 if (tokenType
== null ||
288 tokenType
== XmlTokenType
.XML_END_TAG_START
||
289 tokenType
== XmlTokenType
.XML_EMPTY_ELEMENT_END
||
290 tokenType
== XmlTokenType
.XML_START_TAG_START
) {
294 if (XmlElementType
.XML_NAME
== tokenType
) {
295 final String attrName
= data
.subSequence(lexer
.getTokenStart(), lexer
.getTokenEnd()).toString();
296 if (HREF_ATTR
.equalsIgnoreCase(attrName
)) {
297 href
= parseAttributeValue(lexer
, data
);
299 else if (MEDIA_ATTR
.equalsIgnoreCase(attrName
)) {
300 media
= parseAttributeValue(lexer
, data
);
302 else if (TYPE_ATTR
.equalsIgnoreCase(attrName
)) {
303 type
= parseAttributeValue(lexer
, data
);
305 else if (REL_ATTR
.equalsIgnoreCase(attrName
)) {
306 rel
= parseAttributeValue(lexer
, data
);
308 else if (TITLE_ATTR
.equalsIgnoreCase(attrName
)) {
309 title
= parseAttributeValue(lexer
, data
);
314 tokenType
= lexer
.getTokenType();
317 addResult(result
, linkTagOffset
, href
, media
, type
, rel
, title
, false);
321 tokenType
= lexer
.getTokenType();
325 private static boolean isHrefScripted(final XmlTag tag
) {
326 final XmlAttribute attribute
= tag
.getAttribute(HREF_ATTR
);
327 if (attribute
!= null) {
328 final XmlAttributeValue value
= attribute
.getValueElement();
330 if (PsiTreeUtil
.getChildOfType(value
, OuterLanguageElement
.class) != null) {
340 private static String
getAttributeValue(final XmlTag tag
, final String attrName
) {
341 final XmlAttribute attribute
= tag
.getAttribute(attrName
);
342 if (attribute
!= null) {
343 final XmlAttributeValue value
= attribute
.getValueElement();
345 if (PsiTreeUtil
.getChildOfType(value
, OuterLanguageElement
.class) == null) {
346 return value
.getValue();
355 private static String
parseAttributeValue(final Lexer lexer
, CharSequence data
) {
357 IElementType tokenType
= lexer
.getTokenType();
358 if (XmlElementType
.XML_EQ
== tokenType
) {
360 tokenType
= lexer
.getTokenType();
362 if (tokenType
== XmlElementType
.XML_ATTRIBUTE_VALUE_START_DELIMITER
) {
364 tokenType
= lexer
.getTokenType();
366 if (XmlElementType
.XML_ATTRIBUTE_VALUE_TOKEN
== tokenType
) {
367 return data
.subSequence(lexer
.getTokenStart(), lexer
.getTokenEnd()).toString();
370 else if (tokenType
!= XmlTokenType
.XML_TAG_END
&& tokenType
!= XmlTokenType
.XML_EMPTY_ELEMENT_END
) {
371 return data
.subSequence(lexer
.getTokenStart(), lexer
.getTokenEnd()).toString();
378 private static void addResult(final List
<LinkInfo
> result
,
380 final String hrefValue
,
381 final String mediaValue
,
382 final String typeValue
,
383 final String relValue
,
384 final String titleValue
,
385 final boolean scripted
) {
386 result
.add(new LinkInfo(offset
, scripted
, hrefValue
, mediaValue
, typeValue
, relValue
, titleValue
));
389 public DataExternalizer
<InfoHolder
<LinkInfo
>> getValueExternalizer() {
390 return myValueExternalizer
;
393 public FileBasedIndex
.InputFilter
getInputFilter() {
394 return myInputFilter
;
397 public int getVersion() {
401 public static class LinkInfo
{
408 public boolean scripted
;
410 public LinkInfo(final int textOffset
,
411 final boolean scriptedRef
,
412 @Nullable final String hrefValue
,
413 @Nullable final String mediaValue
,
414 @Nullable final String typeValue
,
415 @Nullable final String relValue
,
416 @Nullable final String titleValue
) {
418 scripted
= scriptedRef
;
427 public boolean equals(final Object o
) {
428 if (this == o
) return true;
429 if (o
== null || getClass() != o
.getClass()) return false;
431 final LinkInfo linkInfo
= (LinkInfo
)o
;
433 if (offset
!= linkInfo
.offset
) return false;
434 if (scripted
!= linkInfo
.scripted
) return false;
435 if (media
!= null ?
!media
.equals(linkInfo
.media
) : linkInfo
.media
!= null) return false;
436 if (rel
!= null ?
!rel
.equals(linkInfo
.rel
) : linkInfo
.rel
!= null) return false;
437 if (title
!= null ?
!title
.equals(linkInfo
.title
) : linkInfo
.title
!= null) return false;
438 if (type
!= null ?
!type
.equals(linkInfo
.type
) : linkInfo
.type
!= null) return false;
439 if (value
!= null ?
!value
.equals(linkInfo
.value
) : linkInfo
.value
!= null) return false;
445 public int hashCode() {
447 result
= 31 * result
+ (value
!= null ? value
.hashCode() : 0);
448 result
= 31 * result
+ (media
!= null ? media
.hashCode() : 0);
449 result
= 31 * result
+ (type
!= null ? type
.hashCode() : 0);
450 result
= 31 * result
+ (rel
!= null ? rel
.hashCode() : 0);
451 result
= 31 * result
+ (title
!= null ? title
.hashCode() : 0);
452 result
= 31 * result
+ (scripted ?
1 : 0);
457 private static class MyLinkReferenceResult
implements LinkReferenceResult
{
458 private final PsiFileSystemItem
[] myItem
;
459 private final LinkInfo myLinkInfo
;
460 private final PsiFile myPsiFile
;
462 public MyLinkReferenceResult(final PsiFileSystemItem
[] item
, final LinkInfo linkInfo
, final PsiFile psiFile
) {
464 myLinkInfo
= linkInfo
;
468 public PsiFile
getReferencedFile() {
469 return (PsiFile
)myItem
[0];
472 public PsiFile
resolve() {
473 final PsiFile referencedFile
= getReferencedFile();
474 if (referencedFile
!= null) {
475 return referencedFile
;
478 if (myPsiFile
!= null) {
479 final PsiElement psiElement
= myPsiFile
.findElementAt(myLinkInfo
.offset
);
480 if (psiElement
!= null) {
481 final PsiElement parent
= psiElement
.getParent();
482 if (parent
instanceof XmlTag
) {
483 final XmlAttribute attribute
= ((XmlTag
)parent
).getAttribute(HREF_ATTR
);
484 if (attribute
!= null) {
485 final XmlAttributeValue value
= attribute
.getValueElement();
487 final PsiReference
[] references
= value
.getReferences();
488 for (PsiReference reference
: references
) {
489 final PsiElement element
= reference
.resolve();
490 if (element
instanceof PsiFile
) {
491 return (PsiFile
)element
;
503 public boolean isScriptedReference() {
504 return myLinkInfo
.scripted
;
507 public String
getMediaValue() {
508 return myLinkInfo
.media
;
511 public String
getRelValue() {
512 return myLinkInfo
.rel
;
515 public String
getTitleValue() {
516 return myLinkInfo
.title
;
520 static class InfoHolder
<T
> {
523 InfoHolder(final T
[] values
) {
528 public boolean equals(final Object o
) {
529 if (this == o
) return true;
530 if (o
== null || getClass() != o
.getClass()) return false;
532 final InfoHolder that
= (InfoHolder
)o
;
534 if (!Arrays
.equals(myValues
, that
.myValues
)) return false;
540 public int hashCode() {
541 return myValues
!= null ? Arrays
.hashCode(myValues
) : 0;