df905629b7035e7eb9e83b5e35d828fff07083a7
[idea/community.git] / java / java-impl / src / com / intellij / lang / java / JavaDocumentationProvider.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.lang.java;
18
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.editorActions.CodeDocumentationUtil;
21 import com.intellij.codeInsight.javadoc.JavaDocExternalFilter;
22 import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator;
23 import com.intellij.codeInsight.javadoc.JavaDocUtil;
24 import com.intellij.lang.CodeDocumentationAwareCommenter;
25 import com.intellij.lang.LangBundle;
26 import com.intellij.lang.LanguageCommenters;
27 import com.intellij.lang.documentation.CodeDocumentationProvider;
28 import com.intellij.lang.documentation.ExternalDocumentationProvider;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.module.Module;
31 import com.intellij.openapi.module.ModuleUtil;
32 import com.intellij.openapi.project.IndexNotReadyException;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.roots.*;
35 import com.intellij.openapi.util.io.FileUtil;
36 import com.intellij.openapi.vfs.JarFileSystem;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.VirtualFileManager;
39 import com.intellij.openapi.vfs.VirtualFileSystem;
40 import com.intellij.openapi.vfs.ex.http.HttpFileSystem;
41 import com.intellij.psi.*;
42 import com.intellij.psi.impl.beanProperties.BeanPropertyElement;
43 import com.intellij.psi.impl.source.javadoc.PsiDocParamRef;
44 import com.intellij.psi.infos.CandidateInfo;
45 import com.intellij.psi.javadoc.PsiDocComment;
46 import com.intellij.psi.javadoc.PsiDocTag;
47 import com.intellij.psi.util.PsiFormatUtil;
48 import com.intellij.util.StringBuilderSpinAllocator;
49 import com.intellij.util.containers.HashMap;
50 import org.jetbrains.annotations.NonNls;
51 import org.jetbrains.annotations.Nullable;
52
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Map;
56
57 /**
58  * @author Maxim.Mossienko
59  */
60 public class JavaDocumentationProvider implements CodeDocumentationProvider, ExternalDocumentationProvider {
61   private static final Logger LOG = Logger.getInstance("#" + JavaDocumentationProvider.class.getName());
62   private static final String LINE_SEPARATOR = "\n";
63
64   @NonNls private static final String PARAM_TAG = "@param";
65   @NonNls private static final String RETURN_TAG = "@return";
66   @NonNls private static final String THROWS_TAG = "@throws";
67   @NonNls public static final String HTML_EXTENSION = ".html";
68   @NonNls public static final String PACKAGE_SUMMARY_FILE = "package-summary.html";
69
70   public String getQuickNavigateInfo(PsiElement element) {
71     if (element instanceof PsiClass) {
72       return generateClassInfo((PsiClass)element);
73     }
74     else if (element instanceof PsiMethod) {
75       return generateMethodInfo((PsiMethod)element);
76     }
77     else if (element instanceof PsiField) {
78       return generateFieldInfo((PsiField)element);
79     }
80     else if (element instanceof PsiVariable) {
81       return generateVariableInfo((PsiVariable)element);
82     }
83     else if (element instanceof PsiPackage) {
84       return generatePackageInfo((PsiPackage)element);
85     }
86     else if (element instanceof BeanPropertyElement) {
87       return generateMethodInfo(((BeanPropertyElement) element).getMethod());
88     }
89     return null;
90   }
91
92   public List<String> getUrlFor(final PsiElement element, final PsiElement originalElement) {
93     return getExternalJavaDocUrl(element);
94   }
95
96   private static void newLine(StringBuffer buffer) {
97     // Don't know why space has to be added after newline for good text alignment...
98     buffer.append("\n ");
99   }
100
101   private static void generateType(@NonNls StringBuffer buffer, PsiType type, PsiElement context) {
102     if (type instanceof PsiPrimitiveType) {
103       buffer.append(type.getCanonicalText());
104
105       return;
106     }
107
108     if (type instanceof PsiWildcardType) {
109       PsiWildcardType wc = ((PsiWildcardType)type);
110       PsiType bound = wc.getBound();
111
112       buffer.append("?");
113
114       if (bound != null) {
115         buffer.append(wc.isExtends() ? " extends " : " super ");
116         generateType(buffer, bound, context);
117       }
118     }
119
120     if (type instanceof PsiArrayType) {
121       generateType(buffer, ((PsiArrayType)type).getComponentType(), context);
122       if (type instanceof PsiEllipsisType) {
123         buffer.append("...");
124       }
125       else {
126         buffer.append("[]");
127       }
128
129       return;
130     }
131
132     if (type instanceof PsiClassType) {
133       PsiClassType.ClassResolveResult result = ((PsiClassType)type).resolveGenerics();
134       PsiClass psiClass = result.getElement();
135       PsiSubstitutor psiSubst = result.getSubstitutor();
136
137       if (psiClass == null || psiClass instanceof PsiTypeParameter) {
138         buffer.append(type.getPresentableText());
139         return;
140       }
141
142       buffer.append(JavaDocUtil.getShortestClassName(psiClass, context));
143
144       if (psiClass.hasTypeParameters()) {
145         StringBuffer subst = new StringBuffer();
146         boolean goodSubst = true;
147
148         PsiTypeParameter[] params = psiClass.getTypeParameters();
149
150         subst.append("<");
151         for (int i = 0; i < params.length; i++) {
152           PsiType t = psiSubst.substitute(params[i]);
153
154           if (t == null) {
155             goodSubst = false;
156             break;
157           }
158
159           generateType(subst, t, context);
160
161           if (i < params.length - 1) {
162             subst.append(", ");
163           }
164         }
165
166         if (goodSubst) {
167           subst.append(">");
168           String text = subst.toString();
169
170           buffer.append(text);
171         }
172       }
173     }
174   }
175
176   private static void generateInitializer(StringBuffer buffer, PsiVariable variable) {
177     PsiExpression initializer = variable.getInitializer();
178     if (initializer != null) {
179       String text = initializer.getText().trim();
180       int index1 = text.indexOf('\n');
181       if (index1 < 0) index1 = text.length();
182       int index2 = text.indexOf('\r');
183       if (index2 < 0) index2 = text.length();
184       int index = Math.min(index1, index2);
185       boolean trunc = index < text.length();
186       text = text.substring(0, index);
187       buffer.append(" = ");
188       buffer.append(text);
189       if (trunc) {
190         buffer.append("...");
191       }
192     }
193   }
194
195   private static void generateModifiers(StringBuffer buffer, PsiElement element) {
196     String modifiers = PsiFormatUtil.formatModifiers(element, PsiFormatUtil.JAVADOC_MODIFIERS_ONLY);
197
198     if (modifiers.length() > 0) {
199       buffer.append(modifiers);
200       buffer.append(" ");
201     }
202   }
203
204   private static String generatePackageInfo(PsiPackage aPackage) {
205     return aPackage.getQualifiedName();
206   }
207
208   @SuppressWarnings({"HardCodedStringLiteral"})
209   private static String generateClassInfo(PsiClass aClass) {
210     StringBuffer buffer = new StringBuffer();
211
212     if (aClass instanceof PsiAnonymousClass) return LangBundle.message("java.terms.anonymous.class");
213
214     PsiFile file = aClass.getContainingFile();
215     final Module module = ModuleUtil.findModuleForPsiElement(file);
216     if (module != null) {
217       buffer.append('[').append(module.getName()).append("] ");
218     }
219
220     if (file instanceof PsiJavaFile) {
221       String packageName = ((PsiJavaFile)file).getPackageName();
222       if (packageName.length() > 0) {
223         buffer.append(packageName);
224         newLine(buffer);
225       }
226     }
227
228     generateModifiers(buffer, aClass);
229
230     final String classString = aClass.isAnnotationType() ? "java.terms.annotation.interface"
231                                : aClass.isInterface()
232                                  ? "java.terms.interface"
233                                  : aClass instanceof PsiTypeParameter
234                                    ? "java.terms.type.parameter"
235                                    : aClass.isEnum() ? "java.terms.enum" : "java.terms.class";
236     buffer.append(LangBundle.message(classString)).append(" ");
237
238     buffer.append(JavaDocUtil.getShortestClassName(aClass, aClass));
239
240     if (aClass.hasTypeParameters()) {
241       PsiTypeParameter[] parms = aClass.getTypeParameters();
242
243       buffer.append("<");
244
245       for (int i = 0; i < parms.length; i++) {
246         PsiTypeParameter p = parms[i];
247
248         buffer.append(p.getName());
249
250         PsiClassType[] refs = p.getExtendsList().getReferencedTypes();
251
252         if (refs.length > 0) {
253           buffer.append(" extends ");
254
255           for (int j = 0; j < refs.length; j++) {
256             generateType(buffer, refs[j], aClass);
257
258             if (j < refs.length - 1) {
259               buffer.append(" & ");
260             }
261           }
262         }
263
264         if (i < parms.length - 1) {
265           buffer.append(", ");
266         }
267       }
268
269       buffer.append(">");
270     }
271
272     PsiClassType[] refs;
273     if (!aClass.isEnum() && !aClass.isAnnotationType()) {
274       PsiReferenceList extendsList = aClass.getExtendsList();
275       refs = extendsList == null ? PsiClassType.EMPTY_ARRAY : extendsList.getReferencedTypes();
276       if (refs.length > 0 || !aClass.isInterface() && !"java.lang.Object".equals(aClass.getQualifiedName())) {
277         buffer.append(" extends ");
278         if (refs.length == 0) {
279           buffer.append("Object");
280         }
281         else {
282           for (int i = 0; i < refs.length; i++) {
283             generateType(buffer, refs[i], aClass);
284
285             if (i < refs.length - 1) {
286               buffer.append(", ");
287             }
288           }
289         }
290       }
291     }
292
293     refs = aClass.getImplementsListTypes();
294     if (refs.length > 0) {
295       newLine(buffer);
296       buffer.append("implements ");
297       for (int i = 0; i < refs.length; i++) {
298         generateType(buffer, refs[i], aClass);
299
300         if (i < refs.length - 1) {
301           buffer.append(", ");
302         }
303       }
304     }
305
306     return buffer.toString();
307   }
308
309   @SuppressWarnings({"HardCodedStringLiteral"})
310   public static String generateMethodInfo(PsiMethod method) {
311     StringBuffer buffer = new StringBuffer();
312
313     PsiClass parentClass = method.getContainingClass();
314
315     if (parentClass != null) {
316       buffer.append(JavaDocUtil.getShortestClassName(parentClass, method));
317       newLine(buffer);
318     }
319
320     generateModifiers(buffer, method);
321
322     PsiTypeParameter[] params = method.getTypeParameters();
323
324     if (params.length > 0) {
325       buffer.append("<");
326       for (int i = 0; i < params.length; i++) {
327         PsiTypeParameter param = params[i];
328
329         buffer.append(param.getName());
330
331         PsiClassType[] extendees = param.getExtendsList().getReferencedTypes();
332
333         if (extendees.length > 0) {
334           buffer.append(" extends ");
335
336           for (int j = 0; j < extendees.length; j++) {
337             generateType(buffer, extendees[j], method);
338
339             if (j < extendees.length - 1) {
340               buffer.append(" & ");
341             }
342           }
343         }
344
345         if (i < params.length - 1) {
346           buffer.append(", ");
347         }
348       }
349       buffer.append("> ");
350     }
351
352     if (method.getReturnType() != null) {
353       generateType(buffer, method.getReturnType(), method);
354       buffer.append(" ");
355     }
356
357     buffer.append(method.getName());
358
359     buffer.append(" (");
360     PsiParameter[] parms = method.getParameterList().getParameters();
361     for (int i = 0; i < parms.length; i++) {
362       PsiParameter parm = parms[i];
363       generateType(buffer, parm.getType(), method);
364       buffer.append(" ");
365       if (parm.getName() != null) {
366         buffer.append(parm.getName());
367       }
368       if (i < parms.length - 1) {
369         buffer.append(", ");
370       }
371     }
372
373     buffer.append(")");
374
375     PsiClassType[] refs = method.getThrowsList().getReferencedTypes();
376     if (refs.length > 0) {
377       newLine(buffer);
378       buffer.append(" throws ");
379       for (int i = 0; i < refs.length; i++) {
380         PsiClass throwsClass = refs[i].resolve();
381
382         if (throwsClass != null) {
383           buffer.append(JavaDocUtil.getShortestClassName(throwsClass, method));
384         }
385         else {
386           buffer.append(refs[i].getPresentableText());
387         }
388
389         if (i < refs.length - 1) {
390           buffer.append(", ");
391         }
392       }
393     }
394
395     return buffer.toString();
396   }
397
398   private static String generateFieldInfo(PsiField field) {
399     StringBuffer buffer = new StringBuffer();
400     PsiClass parentClass = field.getContainingClass();
401
402     if (parentClass != null) {
403       buffer.append(JavaDocUtil.getShortestClassName(parentClass, field));
404       newLine(buffer);
405     }
406
407     generateModifiers(buffer, field);
408
409     generateType(buffer, field.getType(), field);
410     buffer.append(" ");
411     buffer.append(field.getName());
412
413     generateInitializer(buffer, field);
414
415     return buffer.toString();
416   }
417
418   private static String generateVariableInfo(PsiVariable variable) {
419     StringBuffer buffer = new StringBuffer();
420
421     generateModifiers(buffer, variable);
422
423     generateType(buffer, variable.getType(), variable);
424
425     buffer.append(" ");
426
427     buffer.append(variable.getName());
428     generateInitializer(buffer, variable);
429
430     return buffer.toString();
431   }
432
433   public PsiComment findExistingDocComment(final PsiComment _element) {
434     PsiElement parentElement = _element.getParent();
435
436     return parentElement instanceof PsiDocCommentOwner ? ((PsiDocCommentOwner)parentElement).getDocComment() : null;
437   }
438
439   public String generateDocumentationContentStub(PsiComment _element) {
440     PsiElement parentElement = _element.getParent();
441     final Project project = _element.getProject();
442     final StringBuilder builder = StringBuilderSpinAllocator.alloc();
443     try {
444       if (parentElement instanceof PsiMethod) {
445         PsiMethod psiMethod = (PsiMethod)parentElement;
446         final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
447         final CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter)LanguageCommenters.INSTANCE
448           .forLanguage(parentElement.getLanguage());
449         final Map<String, String> param2Description = new HashMap<String, String>();
450         final PsiMethod[] superMethods = psiMethod.findSuperMethods();
451         for (PsiMethod superMethod : superMethods) {
452           final PsiDocComment comment = superMethod.getDocComment();
453           if (comment != null) {
454             final PsiDocTag[] params = comment.findTagsByName("param");
455             for (PsiDocTag param : params) {
456               final PsiElement[] dataElements = param.getDataElements();
457               if (dataElements != null) {
458                 String paramName = null;
459                 for (PsiElement dataElement : dataElements) {
460                   if (dataElement instanceof PsiDocParamRef) {
461                     paramName = dataElement.getReference().getCanonicalText();
462                     break;
463                   }
464                 }
465                 if (paramName != null) {
466                   param2Description.put(paramName, param.getText());
467                 }
468               }
469             }
470           }
471         }
472         for (PsiParameter parameter : parameters) {
473           String description = param2Description.get(parameter.getName());
474           if (description != null) {
475             builder.append(CodeDocumentationUtil.createDocCommentLine("", project, commenter));
476             if (description.indexOf('\n') > -1) description = description.substring(0, description.lastIndexOf('\n'));
477             builder.append(description);
478           }
479           else {
480             builder.append(CodeDocumentationUtil.createDocCommentLine(PARAM_TAG, project, commenter));
481             builder.append(parameter.getName());
482           }
483           builder.append(LINE_SEPARATOR);
484         }
485
486         final PsiTypeParameterList typeParameterList = psiMethod.getTypeParameterList();
487         if (typeParameterList != null) {
488           createTypeParamsListComment(builder, project, commenter, typeParameterList);
489         }
490         if (psiMethod.getReturnType() != null && psiMethod.getReturnType() != PsiType.VOID) {
491           builder.append(CodeDocumentationUtil.createDocCommentLine(RETURN_TAG, project, commenter));
492           builder.append(LINE_SEPARATOR);
493         }
494
495         final PsiJavaCodeReferenceElement[] references = psiMethod.getThrowsList().getReferenceElements();
496         for (PsiJavaCodeReferenceElement reference : references) {
497           builder.append(CodeDocumentationUtil.createDocCommentLine(THROWS_TAG, project, commenter));
498           builder.append(reference.getText());
499           builder.append(LINE_SEPARATOR);
500         }
501       }
502       else if (parentElement instanceof PsiClass) {
503         final PsiTypeParameterList typeParameterList = ((PsiClass)parentElement).getTypeParameterList();
504         if (typeParameterList != null) {
505           final CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter)LanguageCommenters.INSTANCE
506             .forLanguage(parentElement.getLanguage());
507           createTypeParamsListComment(builder, project, commenter, typeParameterList);
508         }
509       }
510       return builder.length() > 0 ? builder.toString() : null;
511     }
512     finally {
513       StringBuilderSpinAllocator.dispose(builder);
514     }
515   }
516
517   private static void createTypeParamsListComment(final StringBuilder buffer,
518                                                   final Project project,
519                                                   final CodeDocumentationAwareCommenter commenter,
520                                                   final PsiTypeParameterList typeParameterList) {
521     final PsiTypeParameter[] typeParameters = typeParameterList.getTypeParameters();
522     for (PsiTypeParameter typeParameter : typeParameters) {
523       buffer.append(CodeDocumentationUtil.createDocCommentLine(PARAM_TAG, project, commenter));
524       buffer.append("<").append(typeParameter.getName()).append(">");
525       buffer.append(LINE_SEPARATOR);
526     }
527   }
528
529   public String generateDoc(final PsiElement element, final PsiElement originalElement) {
530     if (element instanceof PsiMethodCallExpression) {
531       return getMethodCandidateInfo((PsiMethodCallExpression)element);
532     }
533
534
535     //external documentation finder
536     return generateExternalJavadoc(element);
537   }
538
539   public PsiElement getDocumentationElementForLookupItem(final PsiManager psiManager, final Object object, final PsiElement element) {
540     return null;
541   }
542
543   @Nullable
544   public static String generateExternalJavadoc(final PsiElement element) {
545     final JavaDocInfoGenerator javaDocInfoGenerator = new JavaDocInfoGenerator(element.getProject(), element);
546     final List<String> docURLs = getExternalJavaDocUrl(element);
547     return JavaDocExternalFilter.filterInternalDocInfo(javaDocInfoGenerator.generateDocInfo(docURLs));
548   }
549
550
551   @Nullable
552   private static String generateExternalJavadoc(final PsiElement element, String fromUrl, boolean checkCompiled, JavaDocExternalFilter filter) {
553     if (!checkCompiled || element instanceof PsiCompiledElement) {
554       try {
555         String externalDoc = filter.getExternalDocInfoForElement(fromUrl, element);
556         if (externalDoc != null && externalDoc.length() > 0) {
557           return externalDoc;
558         }
559       }
560       catch (Exception e) {
561         //try to generate some javadoc
562       }
563     }
564     return null;
565   }
566
567   private String getMethodCandidateInfo(PsiMethodCallExpression expr) {
568     final PsiResolveHelper rh = JavaPsiFacade.getInstance(expr.getProject()).getResolveHelper();
569     final CandidateInfo[] candidates = rh.getReferencedMethodCandidates(expr, true);
570     final String text = expr.getText();
571     if (candidates.length > 0) {
572       @NonNls final StringBuffer sb = new StringBuffer();
573
574       for (final CandidateInfo candidate : candidates) {
575         final PsiElement element = candidate.getElement();
576
577         if (!(element instanceof PsiMethod)) {
578           continue;
579         }
580
581         final String str = PsiFormatUtil.formatMethod((PsiMethod)element, candidate.getSubstitutor(),
582                                                       PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_TYPE | PsiFormatUtil.SHOW_PARAMETERS,
583                                                       PsiFormatUtil.SHOW_TYPE);
584         createElementLink(sb, element, str);
585       }
586
587       return CodeInsightBundle.message("javadoc.candiates", text, sb);
588     }
589
590     return CodeInsightBundle.message("javadoc.candidates.not.found", text);
591   }
592
593   private static void createElementLink(@NonNls final StringBuffer sb, final PsiElement element, final String str) {
594     sb.append("&nbsp;&nbsp;<a href=\"psi_element://");
595     sb.append(JavaDocUtil.getReferenceText(element.getProject(), element));
596     sb.append("\">");
597     sb.append(str);
598     sb.append("</a>");
599     sb.append("<br>");
600   }
601
602   @Nullable
603   public static List<String> getExternalJavaDocUrl(final PsiElement element) {
604     List<String> urls = null;
605
606     if (element instanceof PsiClass) {
607       urls = findUrlForClass((PsiClass)element);
608     }
609     else if (element instanceof PsiField) {
610       PsiField field = (PsiField)element;
611       PsiClass aClass = field.getContainingClass();
612       if (aClass != null) {
613         urls = findUrlForClass(aClass);
614         if (urls != null) {
615           for (int i = 0; i < urls.size(); i++) {
616             urls.set(i, urls.get(i) + "#" + field.getName());
617           }
618         }
619       }
620     }
621     else if (element instanceof PsiMethod) {
622       PsiMethod method = (PsiMethod)element;
623       PsiClass aClass = method.getContainingClass();
624       if (aClass != null) {
625         final List<String> classUrls = findUrlForClass(aClass);
626
627         if (classUrls != null) {
628           urls = new ArrayList<String>();
629           String signature = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY,
630                                                         PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_PARAMETERS | PsiFormatUtil.SHOW_RAW_TYPE,
631                                                         PsiFormatUtil.SHOW_TYPE | PsiFormatUtil.SHOW_FQ_CLASS_NAMES | PsiFormatUtil.SHOW_RAW_TYPE, 999);
632           for (String classUrl : classUrls) {
633             urls.add(classUrl + "#" + signature);
634           }
635           signature = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY,
636                                                         PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_PARAMETERS,
637                                                         PsiFormatUtil.SHOW_TYPE | PsiFormatUtil.SHOW_FQ_CLASS_NAMES, 999);
638           for (String classUrl : classUrls) {
639             urls.add(classUrl + "#" + signature);
640           }
641         }
642       }
643     }
644     else if (element instanceof PsiPackage) {
645       urls = findUrlForPackage((PsiPackage)element);
646     }
647     else if (element instanceof PsiDirectory) {
648       PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(((PsiDirectory)element));
649       if (aPackage != null) {
650         urls = findUrlForPackage(aPackage);
651       }
652     }
653
654     if (urls == null) {
655       return null;
656     }
657     else {
658       for (int i = 0; i < urls.size(); i++) {
659         urls.set(i, FileUtil.toSystemIndependentName(urls.get(i)));
660       }
661       return urls;
662     }
663   }
664
665   @Nullable
666   public static List<String> findUrlForClass(PsiClass aClass) {
667     String qName = aClass.getQualifiedName();
668     if (qName == null) return null;
669     PsiFile file = aClass.getContainingFile();
670     if (!(file instanceof PsiJavaFile)) return null;
671     String packageName = ((PsiJavaFile)file).getPackageName();
672
673     String relPath;
674     if (packageName.length() > 0) {
675       relPath = packageName.replace('.', '/') + '/' + qName.substring(packageName.length() + 1) + HTML_EXTENSION;
676     }
677     else {
678       relPath = qName + HTML_EXTENSION;
679     }
680
681     final PsiFile containingFile = aClass.getContainingFile();
682     if (containingFile == null) return null;
683     final VirtualFile virtualFile = containingFile.getVirtualFile();
684     if (virtualFile == null) return null;
685
686     return findUrlForVirtualFile(containingFile.getProject(), virtualFile, relPath);
687   }
688
689   @Nullable
690   public static List<String> findUrlForVirtualFile(final Project project, final VirtualFile virtualFile, final String relPath) {
691     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
692     Module module = fileIndex.getModuleForFile(virtualFile);
693     if (module == null) {
694       final VirtualFileSystem fs = virtualFile.getFileSystem();
695       if (fs instanceof JarFileSystem) {
696         final VirtualFile jar = ((JarFileSystem)fs).getVirtualFileForJar(virtualFile);
697         if (jar != null) {
698           module = fileIndex.getModuleForFile(jar);
699         }
700       }
701     }
702     if (module != null) {
703       String[] javadocPaths = ModuleRootManager.getInstance(module).getRootUrls(JavadocOrderRootType.getInstance());
704       List<String> httpRoot = getHttpRoots(javadocPaths, relPath);
705       if (httpRoot != null) return httpRoot;
706     }
707
708     final List<OrderEntry> orderEntries = fileIndex.getOrderEntriesForFile(virtualFile);
709     for (OrderEntry orderEntry : orderEntries) {
710       final String[] files = orderEntry.getUrls(JavadocOrderRootType.getInstance());
711       final List<String> httpRoot = getHttpRoots(files, relPath);
712       if (httpRoot != null) return httpRoot;
713     }
714     return null;
715   }
716
717   @Nullable
718   public static List<String> getHttpRoots(final String[] roots, String relPath) {
719     final ArrayList<String> result = new ArrayList<String>();
720     for (String root : roots) {
721       final VirtualFile virtualFile = VirtualFileManager.getInstance().findFileByUrl(root);
722       if (virtualFile != null) {
723         if (virtualFile.getFileSystem() instanceof HttpFileSystem) {
724           String url = virtualFile.getUrl();
725           if (!url.endsWith("/")) url += "/";
726           result.add(url + relPath);
727         }
728         else {
729           VirtualFile file = virtualFile.findFileByRelativePath(relPath);
730           if (file != null) result.add(file.getUrl());
731         }
732       }
733     }
734
735     return result.isEmpty() ? null : result;
736   }
737
738   @Nullable
739   public static List<String> findUrlForPackage(PsiPackage aPackage) {
740     String qName = aPackage.getQualifiedName();
741     qName = qName.replace('.', '/') + '/' + PACKAGE_SUMMARY_FILE;
742     for (PsiDirectory directory : aPackage.getDirectories()) {
743       List<String> url = findUrlForVirtualFile(aPackage.getProject(), directory.getVirtualFile(), qName);
744       if (url != null) {
745         return url;
746       }
747     }
748     return null;
749   }
750
751   public PsiElement getDocumentationElementForLink(final PsiManager psiManager, final String link, final PsiElement context) {
752     return JavaDocUtil.findReferenceTarget(psiManager, link, context);
753   }
754
755   public String fetchExternalDocumentation(final Project project, PsiElement element, final List<String> docUrls) {
756     return fetchExternalJavadoc(element, project, docUrls);
757   }
758
759   public static String fetchExternalJavadoc(PsiElement element, final Project project, final List<String> docURLs) {
760     final JavaDocExternalFilter docFilter = new JavaDocExternalFilter(project);
761
762     if (docURLs != null) {
763       for (String docURL : docURLs) {
764         try {
765           final String javadoc = generateExternalJavadoc(element, docURL, true, docFilter);
766           if (javadoc != null) return javadoc;
767         }
768         catch (IndexNotReadyException e) {
769           throw e;
770         }
771         catch (Exception e) {
772           LOG.info(e); //connection problems should be ignored
773         }
774       }
775     }
776     return null;
777   }
778 }