[java] groups JDK root collection code for easier modification
[idea/community.git] / java / java-impl / src / com / intellij / openapi / projectRoots / impl / JavaSdkImpl.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.openapi.projectRoots.impl;
17
18 import com.intellij.execution.configurations.GeneralCommandLine;
19 import com.intellij.execution.util.ExecUtil;
20 import com.intellij.icons.AllIcons;
21 import com.intellij.lang.LangBundle;
22 import com.intellij.openapi.Disposable;
23 import com.intellij.openapi.actionSystem.DataKey;
24 import com.intellij.openapi.application.PathManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
27 import com.intellij.openapi.fileTypes.FileTypeManager;
28 import com.intellij.openapi.fileTypes.FileTypes;
29 import com.intellij.openapi.project.ProjectBundle;
30 import com.intellij.openapi.projectRoots.*;
31 import com.intellij.openapi.roots.AnnotationOrderRootType;
32 import com.intellij.openapi.roots.JavadocOrderRootType;
33 import com.intellij.openapi.roots.OrderRootType;
34 import com.intellij.openapi.roots.RootProvider;
35 import com.intellij.openapi.util.SystemInfo;
36 import com.intellij.openapi.util.io.FileUtil;
37 import com.intellij.openapi.util.text.StringUtil;
38 import com.intellij.openapi.vfs.*;
39 import com.intellij.openapi.vfs.jrt.JrtFileSystem;
40 import com.intellij.util.IncorrectOperationException;
41 import com.intellij.util.containers.ContainerUtil;
42 import com.intellij.util.containers.HashMap;
43 import org.jdom.Element;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46 import org.jetbrains.annotations.TestOnly;
47 import org.jetbrains.jps.model.java.impl.JavaSdkUtil;
48
49 import javax.swing.*;
50 import java.io.File;
51 import java.io.FileFilter;
52 import java.util.*;
53 import java.util.regex.Matcher;
54 import java.util.regex.Pattern;
55
56 /**
57  * @author Eugene Zhuravlev
58  * @since Sep 17, 2004
59  */
60 public class JavaSdkImpl extends JavaSdk {
61   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.impl.JavaSdkImpl");
62
63   public static final DataKey<Boolean> KEY = DataKey.create("JavaSdk");
64
65   private static final String VM_EXE_NAME = "java";   // do not use JavaW.exe for Windows because of issues with encoding
66   private static final Pattern VERSION_STRING_PATTERN = Pattern.compile("^(.*)java version \"([1234567890_.]*)\"(.*)$");
67   private static final String JAVA_VERSION_PREFIX = "java version ";
68   private static final String OPENJDK_VERSION_PREFIX = "openjdk version ";
69
70   public JavaSdkImpl(final VirtualFileManager fileManager, final FileTypeManager fileTypeManager) {
71     super("JavaSDK");
72
73     fileManager.addVirtualFileListener(new VirtualFileAdapter() {
74       @Override
75       public void fileDeleted(@NotNull VirtualFileEvent event) {
76         updateCache(event);
77       }
78
79       @Override
80       public void contentsChanged(@NotNull VirtualFileEvent event) {
81         updateCache(event);
82       }
83
84       @Override
85       public void fileCreated(@NotNull VirtualFileEvent event) {
86         updateCache(event);
87       }
88
89       private void updateCache(VirtualFileEvent event) {
90         final VirtualFile file = event.getFile();
91         if (FileTypes.ARCHIVE.equals(fileTypeManager.getFileTypeByFileName(event.getFileName()))) {
92           final String filePath = file.getPath();
93           synchronized (myCachedVersionStrings) {
94             for (String sdkHome : myCachedVersionStrings.keySet()) {
95               if (FileUtil.isAncestor(sdkHome, filePath, false)) {
96                 myCachedVersionStrings.remove(sdkHome);
97                 break;
98               }
99             }
100           }
101         }
102       }
103     });
104   }
105
106   @NotNull
107   @Override
108   public String getPresentableName() {
109     return ProjectBundle.message("sdk.java.name");
110   }
111
112   @Override
113   public Icon getIcon() {
114     return AllIcons.Nodes.PpJdk;
115   }
116
117   @NotNull
118   @Override
119   public String getHelpTopic() {
120     return "reference.project.structure.sdk.java";
121   }
122
123   @NotNull
124   @Override
125   public Icon getIconForAddAction() {
126     return AllIcons.General.AddJdk;
127   }
128
129   @Override
130   @Nullable
131   public String getDefaultDocumentationUrl(@NotNull final Sdk sdk) {
132     final JavaSdkVersion version = getVersion(sdk);
133     if (version == JavaSdkVersion.JDK_1_5) {
134       return "http://docs.oracle.com/javase/1.5.0/docs/api/";
135     }
136     if (version == JavaSdkVersion.JDK_1_6) {
137       return "http://docs.oracle.com/javase/6/docs/api/";
138     }
139     if (version == JavaSdkVersion.JDK_1_7) {
140       return "http://docs.oracle.com/javase/7/docs/api/";
141     }
142     if (version == JavaSdkVersion.JDK_1_8) {
143       return "http://docs.oracle.com/javase/8/docs/api";
144     }
145     return null;
146   }
147
148   @Nullable
149   @Override
150   public String getDownloadSdkUrl() {
151     return "http://www.oracle.com/technetwork/java/javase/downloads/index.html";
152   }
153
154   @Override
155   public AdditionalDataConfigurable createAdditionalDataConfigurable(@NotNull SdkModel sdkModel, @NotNull SdkModificator sdkModificator) {
156     return null;
157   }
158
159   @Override
160   public void saveAdditionalData(@NotNull SdkAdditionalData additionalData, @NotNull Element additional) {
161   }
162
163   @Override
164   public String getBinPath(@NotNull Sdk sdk) {
165     return getConvertedHomePath(sdk) + "bin";
166   }
167
168   @Override
169   public String getToolsPath(@NotNull Sdk sdk) {
170     final String versionString = sdk.getVersionString();
171     final boolean isJdk1_x = versionString != null && (versionString.contains("1.0") || versionString.contains("1.1"));
172     return getConvertedHomePath(sdk) + "lib" + File.separator + (isJdk1_x? "classes.zip" : "tools.jar");
173   }
174
175   @Override
176   public String getVMExecutablePath(@NotNull Sdk sdk) {
177     return getBinPath(sdk) + File.separator + VM_EXE_NAME;
178   }
179
180   private static String getConvertedHomePath(Sdk sdk) {
181     String homePath = sdk.getHomePath();
182     assert homePath != null : sdk;
183     String path = FileUtil.toSystemDependentName(homePath);
184     if (!path.endsWith(File.separator)) {
185       path += File.separator;
186     }
187     return path;
188   }
189
190   @Override
191   public String suggestHomePath() {
192     if (SystemInfo.isMac) {
193       if (new File("/usr/libexec/java_home").canExecute()) {
194         String path = ExecUtil.execAndReadLine(new GeneralCommandLine("/usr/libexec/java_home"));
195         if (path != null && new File(path).isDirectory()) {
196           return path;
197         }
198       }
199
200       String home = checkKnownLocations("/Library/Java/JavaVirtualMachines", "/System/Library/Java/JavaVirtualMachines");
201       if (home != null) return home;
202     }
203
204     if (SystemInfo.isLinux) {
205       String home = checkKnownLocations("/usr/java", "/opt/java", "/usr/lib/jvm");
206       if (home != null) return home;
207     }
208
209     if (SystemInfo.isSolaris) {
210       String home = checkKnownLocations("/usr/jdk");
211       if (home != null) return home;
212     }
213
214     String property = System.getProperty("java.home");
215     if (property != null) {
216       File javaHome = new File(property);
217       if (javaHome.getName().equals("jre")) {
218         javaHome = javaHome.getParentFile();
219       }
220       if (javaHome != null && javaHome.isDirectory()) {
221         return javaHome.getAbsolutePath();
222       }
223     }
224
225     return null;
226   }
227
228   @Nullable
229   private static String checkKnownLocations(String... locations) {
230     for (String home : locations) {
231       if (new File(home).isDirectory()) {
232         return home;
233       }
234     }
235
236     return null;
237   }
238
239   @NotNull
240   @Override
241   public Collection<String> suggestHomePaths() {
242     if (!SystemInfo.isWindows)
243       return Collections.singletonList(suggestHomePath());
244
245     String property = System.getProperty("java.home");
246     if (property == null)
247       return Collections.emptyList();
248
249     File javaHome = new File(property).getParentFile();//actually java.home points to to jre home
250     if (javaHome == null || !javaHome.isDirectory() || javaHome.getParentFile() == null) {
251       return Collections.emptyList();
252     }
253     ArrayList<String> result = new ArrayList<>();
254     File javasFolder = javaHome.getParentFile();
255     scanFolder(javasFolder, result);
256     File parentFile = javasFolder.getParentFile();
257     File root = parentFile != null ? parentFile.getParentFile() : null;
258     String name = parentFile != null ? parentFile.getName() : "";
259     if (name.contains("Program Files") && root != null) {
260       String x86Suffix = " (x86)";
261       boolean x86 = name.endsWith(x86Suffix) && name.length() > x86Suffix.length();
262       File anotherJavasFolder;
263       if (x86) {
264         anotherJavasFolder = new File(root, name.substring(0, name.length() - x86Suffix.length()));
265       }
266       else {
267         anotherJavasFolder = new File(root, name + x86Suffix);
268       }
269       if (anotherJavasFolder.isDirectory()) {
270         scanFolder(new File(anotherJavasFolder, javasFolder.getName()), result);
271       }
272     }
273     return result;
274   }
275
276   private static void scanFolder(File javasFolder, List<String> result) {
277     @SuppressWarnings("RedundantCast") File[] candidates = javasFolder.listFiles((FileFilter)JdkUtil::checkForJdk);
278     if (candidates != null) {
279       for (File file : candidates) {
280         result.add(file.getAbsolutePath());
281       }
282     }
283   }
284
285   @NotNull
286   @Override
287   public FileChooserDescriptor getHomeChooserDescriptor() {
288     final FileChooserDescriptor baseDescriptor = super.getHomeChooserDescriptor();
289     final FileChooserDescriptor descriptor = new FileChooserDescriptor(baseDescriptor) {
290       @Override
291       public void validateSelectedFiles(VirtualFile[] files) throws Exception {
292         if (files.length > 0 && !JrtFileSystem.isSupported()) {
293           String path = files[0].getPath();
294           if (JrtFileSystem.isModularJdk(path) || JrtFileSystem.isModularJdk(adjustSelectedSdkHome(path))) {
295             throw new Exception(LangBundle.message("jrt.not.available.message"));
296           }
297         }
298         baseDescriptor.validateSelectedFiles(files);
299       }
300     };
301     descriptor.putUserData(KEY, Boolean.TRUE);
302     return descriptor;
303   }
304
305   @NotNull
306   @Override
307   public String adjustSelectedSdkHome(@NotNull String homePath) {
308     if (SystemInfo.isMac) {
309       File home = new File(homePath, "/Home");
310       if (home.exists()) return home.getPath();
311
312       home = new File(homePath, "Contents/Home");
313       if (home.exists()) return home.getPath();
314     }
315
316     return homePath;
317   }
318
319   @Override
320   public boolean isValidSdkHome(String path) {
321     return checkForJdk(new File(path)) &&
322            (!JrtFileSystem.isModularJdk(path) || JrtFileSystem.isSupported());
323   }
324
325   @Override
326   public String suggestSdkName(String currentSdkName, String sdkHome) {
327     final String suggestedName;
328     if (currentSdkName != null && !currentSdkName.isEmpty()) {
329       final Matcher matcher = VERSION_STRING_PATTERN.matcher(currentSdkName);
330       final boolean replaceNameWithVersion = matcher.matches();
331       if (replaceNameWithVersion){
332         // user did not change name -> set it automatically
333         final String versionString = getVersionString(sdkHome);
334         suggestedName = versionString == null ? currentSdkName : matcher.replaceFirst("$1" + versionString + "$3");
335       }
336       else {
337         suggestedName = currentSdkName;
338       }
339     }
340     else {
341       String versionString = getVersionString(sdkHome);
342       suggestedName = versionString == null ? ProjectBundle.message("sdk.java.unknown.name") : getVersionNumber(versionString);
343     }
344     return suggestedName;
345   }
346
347   @NotNull
348   private static String getVersionNumber(@NotNull String versionString) {
349     if (versionString.startsWith(JAVA_VERSION_PREFIX) || versionString.startsWith(OPENJDK_VERSION_PREFIX)) {
350       boolean openJdk = versionString.startsWith(OPENJDK_VERSION_PREFIX);
351       versionString = versionString.substring(openJdk ? OPENJDK_VERSION_PREFIX.length() : JAVA_VERSION_PREFIX.length());
352       if (versionString.startsWith("\"") && versionString.endsWith("\"")) {
353         versionString = versionString.substring(1, versionString.length() - 1);
354       }
355       int dotIdx = versionString.indexOf('.');
356       if (dotIdx > 0) {
357         try {
358           int major = Integer.parseInt(versionString.substring(0, dotIdx));
359           int minorDot = versionString.indexOf('.', dotIdx + 1);
360           if (minorDot > 0) {
361             int minor = Integer.parseInt(versionString.substring(dotIdx + 1, minorDot));
362             versionString = major + "." + minor;
363           }
364         }
365         catch (NumberFormatException e) {
366           // Do nothing. Use original version string if failed to parse according to major.minor pattern.
367         }
368       }
369     }
370     return versionString;
371   }
372
373   @Override
374   public void setupSdkPaths(@NotNull Sdk sdk) {
375     String homePath = sdk.getHomePath();
376     assert homePath != null : sdk;
377     File jdkHome = new File(homePath);
378     SdkModificator sdkModificator = sdk.getSdkModificator();
379
380     List<VirtualFile> classes = findClasses(jdkHome, false);
381     Set<VirtualFile> previousRoots = new LinkedHashSet<>(Arrays.asList(sdkModificator.getRoots(OrderRootType.CLASSES)));
382     sdkModificator.removeRoots(OrderRootType.CLASSES);
383     previousRoots.removeAll(new HashSet<>(classes));
384     for (VirtualFile aClass : classes) {
385       sdkModificator.addRoot(aClass, OrderRootType.CLASSES);
386     }
387     for (VirtualFile root : previousRoots) {
388       sdkModificator.addRoot(root, OrderRootType.CLASSES);
389     }
390
391     addSources(jdkHome, sdkModificator);
392     addDocs(jdkHome, sdkModificator, sdk);
393     attachJdkAnnotations(sdkModificator);
394
395     sdkModificator.commitChanges();
396   }
397
398   public static void attachJdkAnnotations(@NotNull SdkModificator modificator) {
399     LocalFileSystem lfs = LocalFileSystem.getInstance();
400     List<String> pathsChecked = new ArrayList<>();
401     // community idea under idea
402     String path = FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/java/jdkAnnotations";
403     VirtualFile root = lfs.findFileByPath(path);
404     pathsChecked.add(path);
405
406     if (root == null) {  // idea under idea
407       path = FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/community/java/jdkAnnotations";
408       root = lfs.findFileByPath(path);
409       pathsChecked.add(path);
410     }
411     if (root == null) { // build
412       String url = "jar://" + FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar!/";
413       root = VirtualFileManager.getInstance().findFileByUrl(url);
414       pathsChecked.add(FileUtil.toSystemIndependentName(PathManager.getHomePath()) + "/lib/jdkAnnotations.jar");
415     }
416     if (root == null) {
417       String msg = "Paths checked:\n";
418       for (String p : pathsChecked) {
419         File file = new File(p);
420         msg += "Path: '"+p+"' "+(file.exists() ? "Found" : "Not found")+"; directory children: "+Arrays.toString(file.getParentFile().listFiles())+"\n";
421       }
422       LOG.error("JDK annotations not found", msg);
423       return;
424     }
425
426     OrderRootType annoType = AnnotationOrderRootType.getInstance();
427     modificator.removeRoot(root, annoType);
428     modificator.addRoot(root, annoType);
429   }
430
431   private final Map<String, String> myCachedVersionStrings = Collections.synchronizedMap(new HashMap<String, String>());
432
433   @Override
434   public final String getVersionString(String sdkHome) {
435     String versionString = myCachedVersionStrings.get(sdkHome);
436     if (versionString == null) {
437       versionString = getJdkVersion(sdkHome);
438       if (!StringUtil.isEmpty(versionString)) {
439         myCachedVersionStrings.put(sdkHome, versionString);
440       }
441     }
442     return versionString;
443   }
444
445   @Override
446   public JavaSdkVersion getVersion(@NotNull Sdk sdk) {
447     String version = sdk.getVersionString();
448     if (version == null) return null;
449     return JdkVersionUtil.getVersion(version);
450   }
451
452   @Override
453   @Nullable
454   public JavaSdkVersion getVersion(@NotNull String versionString) {
455     return JdkVersionUtil.getVersion(versionString);
456   }
457
458   @Override
459   public boolean isOfVersionOrHigher(@NotNull Sdk sdk, @NotNull JavaSdkVersion version) {
460     JavaSdkVersion sdkVersion = getVersion(sdk);
461     return sdkVersion != null && sdkVersion.isAtLeast(version);
462   }
463
464   @NotNull
465   @Override
466   public Sdk createJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) {
467     ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this);
468     SdkModificator sdkModificator = jdk.getSdkModificator();
469
470     String path = home.replace(File.separatorChar, '/');
471     sdkModificator.setHomePath(path);
472     sdkModificator.setVersionString(jdkName); // must be set after home path, otherwise setting home path clears the version string
473
474     File jdkHomeFile = new File(home);
475     addClasses(jdkHomeFile, sdkModificator, isJre);
476     addSources(jdkHomeFile, sdkModificator);
477     addDocs(jdkHomeFile, sdkModificator, null);
478     sdkModificator.commitChanges();
479
480     return jdk;
481   }
482
483   @NotNull
484   @TestOnly
485   public Sdk createMockJdk(@NotNull String jdkName, @NotNull String home, boolean isJre) {
486     String homePath = home.replace(File.separatorChar, '/');
487     File jdkHomeFile = new File(homePath);
488
489     ProjectRootContainerImpl rootContainer = new ProjectRootContainerImpl(true);
490     SdkModificator sdkModificator = new SdkModificator() {
491       @Override public String getName() { throw new UnsupportedOperationException(); }
492       @Override public void setName(String name) { throw new UnsupportedOperationException(); }
493       @Override public String getHomePath() { throw new UnsupportedOperationException(); }
494       @Override public void setHomePath(String path) { throw new UnsupportedOperationException(); }
495       @Override public String getVersionString() { throw new UnsupportedOperationException(); }
496       @Override public void setVersionString(String versionString) { throw new UnsupportedOperationException(); }
497       @Override public SdkAdditionalData getSdkAdditionalData() { throw new UnsupportedOperationException(); }
498       @Override public void setSdkAdditionalData(SdkAdditionalData data) { throw new UnsupportedOperationException(); }
499       @Override public VirtualFile[] getRoots(OrderRootType rootType) { throw new UnsupportedOperationException(); }
500       @Override public void removeRoot(VirtualFile root, OrderRootType rootType) { throw new UnsupportedOperationException(); }
501       @Override public void removeRoots(OrderRootType rootType) { throw new UnsupportedOperationException(); }
502       @Override public void removeAllRoots() { throw new UnsupportedOperationException(); }
503       @Override public void commitChanges() { throw new UnsupportedOperationException(); }
504       @Override public boolean isWritable() { throw new UnsupportedOperationException(); }
505
506       @Override
507       public void addRoot(VirtualFile root, OrderRootType rootType) {
508         rootContainer.addRoot(root, rootType);
509       }
510     };
511
512     rootContainer.startChange();
513     addClasses(jdkHomeFile, sdkModificator, isJre);
514     addSources(jdkHomeFile, sdkModificator);
515     rootContainer.finishChange();
516
517     ProjectJdkImpl jdk = new ProjectJdkImpl(jdkName, this, homePath, jdkName) {
518       @Override
519       public void setName(@NotNull String name) {
520         throwReadOnly();
521       }
522
523       @Override
524       public void readExternal(@NotNull Element element) {
525         throwReadOnly();
526       }
527
528       @Override
529       public void readExternal(@NotNull Element element, @Nullable ProjectJdkTable projectJdkTable) {
530         throwReadOnly();
531       }
532
533       @NotNull
534       @Override
535       public SdkModificator getSdkModificator() {
536         throwReadOnly();
537         return null;
538       }
539
540       @Override
541       public void setSdkAdditionalData(SdkAdditionalData data) {
542         throwReadOnly();
543       }
544
545       @Override
546       public void addRoot(VirtualFile root, OrderRootType rootType) {
547         throwReadOnly();
548       }
549
550       @Override
551       public void removeRoot(VirtualFile root, OrderRootType rootType) {
552         throwReadOnly();
553       }
554
555       @Override
556       public void removeRoots(OrderRootType rootType) {
557         throwReadOnly();
558       }
559
560       @Override
561       public void removeAllRoots() {
562         throwReadOnly();
563       }
564
565       @Override
566       public boolean isWritable() {
567         return false;
568       }
569
570       @Override
571       public void update() {
572         throwReadOnly();
573       }
574
575       @Override
576       public VirtualFile[] getRoots(OrderRootType rootType) {
577         return rootContainer.getRootFiles(rootType);
578       }
579
580       @NotNull
581       @Override
582       public RootProvider getRootProvider() {
583         return new RootProvider() {
584           @NotNull
585           @Override
586           public String[] getUrls(@NotNull OrderRootType rootType) {
587             return ContainerUtil.map2Array(getFiles(rootType), String.class, VirtualFile::getUrl);
588           }
589
590           @NotNull
591           @Override
592           public VirtualFile[] getFiles(@NotNull OrderRootType rootType) {
593             return getRoots(rootType);
594           }
595
596           @Override
597           public void addRootSetChangedListener(@NotNull RootSetChangedListener listener) { }
598
599           @Override
600           public void addRootSetChangedListener(@NotNull RootSetChangedListener listener, @NotNull Disposable parentDisposable) { }
601
602           @Override
603           public void removeRootSetChangedListener(@NotNull RootSetChangedListener listener) { }
604         };
605       }
606
607       private void throwReadOnly() {
608         throw new IncorrectOperationException("Can't modify, MockJDK is read-only, consider calling .clone() first");
609       }
610     };
611
612     ProjectJdkImpl.copyRoots(rootContainer, jdk);
613     return jdk;
614   }
615
616   private static void addClasses(File file, SdkModificator sdkModificator, boolean isJre) {
617     for (VirtualFile virtualFile : findClasses(file, isJre)) {
618       sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES);
619     }
620   }
621
622   @NotNull
623   private static List<VirtualFile> findClasses(File file, boolean isJre) {
624     List<VirtualFile> result = ContainerUtil.newArrayList();
625     VirtualFileManager fileManager = VirtualFileManager.getInstance();
626
627     VirtualFile jrt = fileManager.findFileByUrl(JrtFileSystem.PROTOCOL_PREFIX + getPath(file) + JrtFileSystem.SEPARATOR);
628     if (jrt != null) {
629       ContainerUtil.addAll(result, jrt.getChildren());
630     }
631
632     for (File root : JavaSdkUtil.getJdkClassesRoots(file, isJre)) {
633       String url = VfsUtil.getUrlForLibraryRoot(root);
634       ContainerUtil.addIfNotNull(result, fileManager.findFileByUrl(url));
635     }
636
637     Collections.sort(result, Comparator.comparing(VirtualFile::getPath));
638
639     return result;
640   }
641
642   private static void addSources(@NotNull File jdkHome, @NotNull SdkModificator sdkModificator) {
643     VirtualFile jdkSrc = findSources(jdkHome, "src");
644     if (jdkSrc != null) {
645       sdkModificator.addRoot(jdkSrc, OrderRootType.SOURCES);
646     }
647
648     VirtualFile fxSrc = findSources(jdkHome, "javafx-src");
649     if (fxSrc != null) {
650       sdkModificator.addRoot(fxSrc, OrderRootType.SOURCES);
651     }
652   }
653
654   @Nullable
655   private static VirtualFile findSources(File jdkHome, String srcName) {
656     File srcArc = new File(jdkHome, srcName + ".jar");
657     if (!srcArc.exists()) srcArc = new File(jdkHome, srcName + ".zip");
658     if (srcArc.exists()) {
659       VirtualFile vFile = findInJar(srcArc, "src");
660       if (vFile == null) vFile = findInJar(srcArc, "");
661       return vFile;
662     }
663
664     File srcDir = new File(jdkHome, "src");
665     if (srcDir.isDirectory()) {
666       return LocalFileSystem.getInstance().findFileByPath(getPath(srcDir));
667     }
668
669     return null;
670   }
671
672   private void addDocs(File jdkHome, SdkModificator sdkModificator, @Nullable Sdk sdk) {
673     OrderRootType docRootType = JavadocOrderRootType.getInstance();
674
675     VirtualFile apiDocs = findDocs(jdkHome, "docs/api");
676     if (apiDocs != null) {
677       sdkModificator.addRoot(apiDocs, docRootType);
678     }
679     else if (SystemInfo.isMac) {
680       VirtualFile commonDocs = findDocs(jdkHome, "docs");
681       if (commonDocs == null) commonDocs = findInJar(new File(jdkHome, "docs.jar"), "doc/api");
682       if (commonDocs == null) commonDocs = findInJar(new File(jdkHome, "docs.jar"), "docs/api");
683       if (commonDocs != null) {
684         sdkModificator.addRoot(commonDocs, docRootType);
685       }
686
687       VirtualFile appleDocs = findDocs(jdkHome, "appledocs");
688       if (appleDocs == null) appleDocs = findInJar(new File(jdkHome, "appledocs.jar"), "appledoc/api");
689       if (appleDocs != null) {
690         sdkModificator.addRoot(appleDocs, docRootType);
691       }
692     }
693
694     if (sdk != null && sdkModificator.getRoots(docRootType).length == 0 && sdkModificator.getRoots(OrderRootType.SOURCES).length == 0) {
695       // registers external docs when both sources and local docs are missing
696       String docUrl = getDefaultDocumentationUrl(sdk);
697       if (docUrl != null) {
698         VirtualFile onlineDoc = VirtualFileManager.getInstance().findFileByUrl(docUrl);
699         if (onlineDoc != null) {
700           sdkModificator.addRoot(onlineDoc, docRootType);
701         }
702       }
703
704       if (getVersion(sdk) == JavaSdkVersion.JDK_1_7) {
705         VirtualFile fxDocUrl = VirtualFileManager.getInstance().findFileByUrl("http://docs.oracle.com/javafx/2/api/");
706         if (fxDocUrl != null) {
707           sdkModificator.addRoot(fxDocUrl, docRootType);
708         }
709       }
710     }
711   }
712
713   @Nullable
714   private static VirtualFile findDocs(@NotNull File jdkHome, @NotNull String relativePath) {
715     File docDir = new File(jdkHome.getAbsolutePath(), relativePath);
716     return docDir.isDirectory() ? LocalFileSystem.getInstance().findFileByPath(getPath(docDir)) : null;
717   }
718
719   private static VirtualFile findInJar(File jarFile, String relativePath) {
720     if (!jarFile.exists()) return null;
721     String url = JarFileSystem.PROTOCOL_PREFIX + getPath(jarFile) + JarFileSystem.JAR_SEPARATOR + relativePath;
722     return VirtualFileManager.getInstance().findFileByUrl(url);
723   }
724
725   private static String getPath(File jarFile) {
726     return jarFile.getAbsolutePath().replace(File.separatorChar, '/');
727   }
728
729   @Override
730   public boolean isRootTypeApplicable(@NotNull OrderRootType type) {
731     return type == OrderRootType.CLASSES ||
732            type == OrderRootType.SOURCES ||
733            type == JavadocOrderRootType.getInstance() ||
734            type == AnnotationOrderRootType.getInstance();
735   }
736 }