update copyright
[fedora-idea.git] / xml / impl / src / com / intellij / html / HtmlLinkTagIndex.java
blobe32fa4e6bff37b188c252aad2191a1b234fa5f17
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.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;
57 /**
58 * @author spleaner
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)) {
73 return false;
76 final FileType fileType = file.getFileType();
77 if (!(fileType instanceof LanguageFileType)) {
78 return false;
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);
107 if (s != null) {
108 out.writeUTF(s);
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() {
133 return INDEX_ID;
136 public interface LinkReferenceResult {
137 @Nullable
138 PsiFile getReferencedFile();
140 @Nullable
141 PsiFile resolve();
143 boolean isScriptedReference();
145 @Nullable
146 String getMediaValue();
148 @Nullable
149 String getRelValue();
151 @Nullable
152 String getTitleValue();
155 @NotNull
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)) {
159 return result;
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) {
184 item[0] = resolved;
189 result.add(new MyLinkReferenceResult(item, linkInfo, psiFile));
193 return true;
195 }, GlobalSearchScope.allScope(project));
197 return result;
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);
210 else {
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);
229 else {
230 psiFile = viewProvider.getPsi(viewProvider.getBaseLanguage());
233 if (psiFile != null) {
234 final XmlRecursiveElementVisitor visitor = new XmlRecursiveElementVisitor() {
235 @Override
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();
265 lexer.start(data);
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();
280 String href = null;
281 String type = null;
282 String media = null;
283 String rel = null;
284 String title = null;
286 while (true) {
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) {
291 break;
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);
313 lexer.advance();
314 tokenType = lexer.getTokenType();
317 addResult(result, linkTagOffset, href, media, type, rel, title, false);
320 lexer.advance();
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();
329 if (value != null) {
330 if (PsiTreeUtil.getChildOfType(value, OuterLanguageElement.class) != null) {
331 return true;
336 return false;
339 @Nullable
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();
344 if (value != null) {
345 if (PsiTreeUtil.getChildOfType(value, OuterLanguageElement.class) == null) {
346 return value.getValue();
351 return null;
354 @Nullable
355 private static String parseAttributeValue(final Lexer lexer, CharSequence data) {
356 lexer.advance();
357 IElementType tokenType = lexer.getTokenType();
358 if (XmlElementType.XML_EQ == tokenType) {
359 lexer.advance();
360 tokenType = lexer.getTokenType();
362 if (tokenType == XmlElementType.XML_ATTRIBUTE_VALUE_START_DELIMITER) {
363 lexer.advance();
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();
375 return null;
378 private static void addResult(final List<LinkInfo> result,
379 final int offset,
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() {
398 return 4;
401 public static class LinkInfo {
402 public int offset;
403 public String value;
404 public String media;
405 public String type;
406 public String rel;
407 public String title;
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) {
417 offset = textOffset;
418 scripted = scriptedRef;
419 value = hrefValue;
420 media = mediaValue;
421 type = typeValue;
422 rel = relValue;
423 title = titleValue;
426 @Override
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;
441 return true;
444 @Override
445 public int hashCode() {
446 int result = offset;
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);
453 return result;
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) {
463 myItem = item;
464 myLinkInfo = linkInfo;
465 myPsiFile = psiFile;
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();
486 if (value != null) {
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;
500 return null;
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> {
521 public T[] myValues;
523 InfoHolder(final T[] values) {
524 myValues = values;
527 @Override
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;
536 return true;
539 @Override
540 public int hashCode() {
541 return myValues != null ? Arrays.hashCode(myValues) : 0;