e2711874aff74037cf9748c6a0c65d6f63ef65c3
[idea/community.git] / platform / core-impl / src / com / intellij / ide / plugins / PluginManagerCore.java
1 /*
2  * Copyright 2000-2014 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.ide.plugins;
17
18 import com.intellij.ide.ClassUtilCore;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.StartupProgress;
21 import com.intellij.ide.plugins.cl.PluginClassLoader;
22 import com.intellij.openapi.application.Application;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.application.PathManager;
25 import com.intellij.openapi.components.ExtensionAreas;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.extensions.*;
28 import com.intellij.openapi.util.*;
29 import com.intellij.openapi.util.io.FileUtil;
30 import com.intellij.openapi.util.io.StreamUtil;
31 import com.intellij.openapi.util.io.ZipFileCache;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.util.*;
34 import com.intellij.util.containers.MultiMap;
35 import com.intellij.util.execution.ParametersListUtil;
36 import com.intellij.util.graph.CachingSemiGraph;
37 import com.intellij.util.graph.DFSTBuilder;
38 import com.intellij.util.graph.Graph;
39 import com.intellij.util.graph.GraphGenerator;
40 import com.intellij.util.xmlb.XmlSerializationException;
41 import gnu.trove.THashMap;
42 import gnu.trove.THashSet;
43 import gnu.trove.TIntProcedure;
44 import org.jdom.Document;
45 import org.jetbrains.annotations.NonNls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48
49 import java.io.*;
50 import java.lang.reflect.InvocationTargetException;
51 import java.lang.reflect.Method;
52 import java.net.MalformedURLException;
53 import java.net.URL;
54 import java.net.URLClassLoader;
55 import java.net.URLDecoder;
56 import java.util.*;
57 import java.util.zip.ZipEntry;
58 import java.util.zip.ZipFile;
59
60 public class PluginManagerCore {
61   @NonNls public static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
62   @NonNls public static final String CORE_PLUGIN_ID = "com.intellij";
63   @NonNls public static final String META_INF = "META-INF";
64   @NonNls public static final String PLUGIN_XML = "plugin.xml";
65   public static final float PLUGINS_PROGRESS_MAX_VALUE = 0.3f;
66   private static final Map<PluginId,Integer> ourId2Index = new THashMap<PluginId, Integer>();
67   @NonNls static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module";
68   private static final Map<String, IdeaPluginDescriptorImpl> ourModulesToContainingPlugins = new THashMap<String, IdeaPluginDescriptorImpl>();
69   private static final PluginClassCache ourPluginClasses = new PluginClassCache();
70   @NonNls static final String SPECIAL_IDEA_PLUGIN = "IDEA CORE";
71   static final String DISABLE = "disable";
72   static final String ENABLE = "enable";
73   static final String EDIT = "edit";
74   @NonNls private static final String PROPERTY_PLUGIN_PATH = "plugin.path";
75   static List<String> ourDisabledPlugins;
76   private static MultiMap<String, String> ourBrokenPluginVersions;
77   private static IdeaPluginDescriptor[] ourPlugins;
78   static String myPluginError;
79   static List<String> myPlugins2Disable = null;
80   static LinkedHashSet<String> myPlugins2Enable = null;
81   public static String BUILD_NUMBER;
82   private static BuildNumber ourBuildNumber;
83
84   /**
85    * do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by IdeaClassLoader
86    */
87   public static synchronized IdeaPluginDescriptor[] getPlugins() {
88     if (ourPlugins == null) {
89       initPlugins(null);
90     }
91     return ourPlugins;
92   }
93
94   public static void loadDisabledPlugins(final String configPath, final Collection<String> disabledPlugins) {
95     final File file = new File(configPath, DISABLED_PLUGINS_FILENAME);
96     if (file.isFile()) {
97       try {
98         BufferedReader reader = new BufferedReader(new FileReader(file));
99         try {
100           String id;
101           while ((id = reader.readLine()) != null) {
102             disabledPlugins.add(id.trim());
103           }
104         }
105         finally {
106           reader.close();
107         }
108       }
109       catch (IOException ignored) { }
110     }
111   }
112
113   @NotNull
114   public static List<String> getDisabledPlugins() {
115     if (ourDisabledPlugins == null) {
116       ourDisabledPlugins = new ArrayList<String>();
117       if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
118         loadDisabledPlugins(PathManager.getConfigPath(), ourDisabledPlugins);
119       }
120     }
121     return ourDisabledPlugins;
122   }
123
124   public static boolean isBrokenPlugin(IdeaPluginDescriptor descriptor) {
125     return getBrokenPluginVersions().get(descriptor.getPluginId().getIdString()).contains(descriptor.getVersion());
126   }
127
128   public static MultiMap<String, String> getBrokenPluginVersions() {
129     if (ourBrokenPluginVersions == null) {
130       ourBrokenPluginVersions = MultiMap.createSet();
131
132       if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
133         BufferedReader br = new BufferedReader(new InputStreamReader(PluginManagerCore.class.getResourceAsStream("/brokenPlugins.txt")));
134         try {
135           String s;
136           while ((s = br.readLine()) != null) {
137             s = s.trim();
138             if (s.startsWith("//")) continue;
139
140             List<String> tokens = ParametersListUtil.parse(s);
141             if (tokens.isEmpty()) continue;
142
143             if (tokens.size() == 1) {
144               throw new RuntimeException("brokenPlugins.txt is broken. The line contains plugin name, but does not contains version: " + s);
145             }
146
147             String pluginId = tokens.get(0);
148             List<String> versions = tokens.subList(1, tokens.size());
149
150             ourBrokenPluginVersions.putValues(pluginId, versions);
151           }
152         }
153         catch (IOException e) {
154           throw new RuntimeException("Failed to read /brokenPlugins.txt", e);
155         }
156         finally {
157           StreamUtil.closeStream(br);
158         }
159       }
160     }
161     return ourBrokenPluginVersions;
162   }
163
164   static boolean isUnitTestMode() {
165     final Application app = ApplicationManager.getApplication();
166     return app != null && app.isUnitTestMode();
167   }
168
169   public static void savePluginsList(@NotNull Collection<String> ids, boolean append, @NotNull File plugins) throws IOException {
170     if (!plugins.isFile()) {
171       FileUtil.ensureCanCreateFile(plugins);
172     }
173     PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins, append)));
174     try {
175       for (String id : ids) {
176         printWriter.println(id);
177       }
178       printWriter.flush();
179     }
180     finally {
181       printWriter.close();
182     }
183   }
184
185   public static boolean disablePlugin(String id) {
186     List<String> disabledPlugins = getDisabledPlugins();
187     if (disabledPlugins.contains(id)) {
188       return false;
189     }
190     disabledPlugins.add(id);
191     try {
192       saveDisabledPlugins(disabledPlugins, false);
193     }
194     catch (IOException e) {
195       return false;
196     }
197     return true;
198   }
199
200   public static boolean enablePlugin(String id) {
201     if (!getDisabledPlugins().contains(id)) return false;
202     getDisabledPlugins().remove(id);
203     try {
204       saveDisabledPlugins(getDisabledPlugins(), false);
205     }
206     catch (IOException e) {
207       return false;
208     }
209     return true;
210   }
211
212   public static void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException {
213     File plugins = new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME);
214     savePluginsList(ids, append, plugins);
215     ourDisabledPlugins = null;
216   }
217
218   public static Logger getLogger() {
219     return LoggerHolder.ourLogger;
220   }
221
222   public static int getPluginLoadingOrder(PluginId id) {
223     return ourId2Index.get(id);
224   }
225
226   public static boolean isModuleDependency(final PluginId dependentPluginId) {
227     return dependentPluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX);
228   }
229
230   public static void checkDependants(final IdeaPluginDescriptor pluginDescriptor,
231                                      final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
232                                      final Condition<PluginId> check) {
233     checkDependants(pluginDescriptor, pluginId2Descriptor, check, new THashSet<PluginId>());
234   }
235
236   private static boolean checkDependants(final IdeaPluginDescriptor pluginDescriptor,
237                                          final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
238                                          final Condition<PluginId> check,
239                                          final Set<PluginId> processed) {
240     processed.add(pluginDescriptor.getPluginId());
241     final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
242     final Set<PluginId> optionalDependencies = new THashSet<PluginId>(Arrays.asList(pluginDescriptor.getOptionalDependentPluginIds()));
243     for (final PluginId dependentPluginId : dependentPluginIds) {
244       if (processed.contains(dependentPluginId)) {
245         continue;
246       }
247
248       if (isModuleDependency(dependentPluginId) && (ourModulesToContainingPlugins.isEmpty() || ourModulesToContainingPlugins.containsKey(
249         dependentPluginId.getIdString()))) {
250         continue;
251       }
252       if (!optionalDependencies.contains(dependentPluginId)) {
253         if (!check.value(dependentPluginId)) {
254           return false;
255         }
256         final IdeaPluginDescriptor dependantPluginDescriptor = pluginId2Descriptor.fun(dependentPluginId);
257         if (dependantPluginDescriptor != null && !checkDependants(dependantPluginDescriptor, pluginId2Descriptor, check, processed)) {
258           return false;
259         }
260       }
261     }
262     return true;
263   }
264
265   public static void addPluginClass(@NotNull String className, PluginId pluginId, boolean loaded) {
266     ourPluginClasses.addPluginClass(className, pluginId, loaded);
267   }
268
269   @Nullable
270   public static PluginId getPluginByClassName(@NotNull String className) {
271     return ourPluginClasses.getPluginByClassName(className);
272   }
273
274   public static void dumpPluginClassStatistics() {
275     ourPluginClasses.dumpPluginClassStatistics();
276   }
277
278   static boolean isDependent(final IdeaPluginDescriptor descriptor,
279                              final PluginId on,
280                              Map<PluginId, IdeaPluginDescriptor> map,
281                              final boolean checkModuleDependencies) {
282     for (PluginId id: descriptor.getDependentPluginIds()) {
283       if (ArrayUtil.contains(id, (Object[])descriptor.getOptionalDependentPluginIds())) {
284         continue;
285       }
286       if (!checkModuleDependencies && isModuleDependency(id)) {
287         continue;
288       }
289       if (id.equals(on)) {
290         return true;
291       }
292       final IdeaPluginDescriptor depDescriptor = map.get(id);
293       if (depDescriptor != null && isDependent(depDescriptor, on, map, checkModuleDependencies)) {
294         return true;
295       }
296     }
297     return false;
298   }
299
300   static boolean hasModuleDependencies(final IdeaPluginDescriptor descriptor) {
301     final PluginId[] dependentPluginIds = descriptor.getDependentPluginIds();
302     for (PluginId dependentPluginId : dependentPluginIds) {
303       if (isModuleDependency(dependentPluginId)) {
304         return true;
305       }
306     }
307     return false;
308   }
309
310   static boolean shouldLoadPlugins() {
311     try {
312       // no plugins during bootstrap
313       Class.forName("com.intellij.openapi.extensions.Extensions");
314     }
315     catch (ClassNotFoundException e) {
316       return false;
317     }
318     //noinspection HardCodedStringLiteral
319     final String loadPlugins = System.getProperty("idea.load.plugins");
320     return loadPlugins == null || Boolean.TRUE.toString().equals(loadPlugins);
321   }
322
323   static void configureExtensions() {
324     Extensions.setLogProvider(new IdeaLogProvider());
325     Extensions.registerAreaClass(ExtensionAreas.IDEA_PROJECT, null);
326     Extensions.registerAreaClass(ExtensionAreas.IDEA_MODULE, ExtensionAreas.IDEA_PROJECT);
327   }
328
329   private static Method getAddUrlMethod(final ClassLoader loader) {
330     return ReflectionUtil.getDeclaredMethod(loader instanceof URLClassLoader ? URLClassLoader.class : loader.getClass(), "addURL", URL.class);
331   }
332
333   @Nullable
334   static ClassLoader createPluginClassLoader(@NotNull File[] classPath,
335                                              @NotNull ClassLoader[] parentLoaders,
336                                              @NotNull IdeaPluginDescriptor pluginDescriptor) {
337
338     if (pluginDescriptor.getUseIdeaClassLoader()) {
339       try {
340         final ClassLoader loader = PluginManagerCore.class.getClassLoader();
341         final Method addUrlMethod = getAddUrlMethod(loader);
342
343
344         for (File aClassPath : classPath) {
345           final File file = aClassPath.getCanonicalFile();
346           addUrlMethod.invoke(loader, file.toURI().toURL());
347         }
348
349         return loader;
350       }
351       catch (IOException e) {
352         e.printStackTrace();
353       }
354       catch (IllegalAccessException e) {
355         e.printStackTrace();
356       }
357       catch (InvocationTargetException e) {
358         e.printStackTrace();
359       }
360     }
361
362     PluginId pluginId = pluginDescriptor.getPluginId();
363     File pluginRoot = pluginDescriptor.getPath();
364
365     //if (classPath.length == 0) return null;
366     if (isUnitTestMode()) return null;
367     try {
368       final List<URL> urls = new ArrayList<URL>(classPath.length);
369       for (File aClassPath : classPath) {
370         final File file = aClassPath.getCanonicalFile(); // it is critical not to have "." and ".." in classpath elements
371         urls.add(file.toURI().toURL());
372       }
373       return new PluginClassLoader(urls, parentLoaders, pluginId, pluginDescriptor.getVersion(), pluginRoot);
374     }
375     catch (MalformedURLException e) {
376       e.printStackTrace();
377     }
378     catch (IOException e) {
379       e.printStackTrace();
380     }
381     return null;
382   }
383
384   public static void invalidatePlugins() {
385     ourPlugins = null;
386     ourDisabledPlugins = null;
387   }
388
389   public static boolean isPluginClass(String className) {
390     return ourPlugins != null && getPluginByClassName(className) != null;
391   }
392
393   static void logPlugins() {
394     List<String> loadedBundled = new ArrayList<String>();
395     List<String> disabled = new ArrayList<String>();
396     List<String> loadedCustom = new ArrayList<String>();
397
398     for (IdeaPluginDescriptor descriptor : ourPlugins) {
399       final String version = descriptor.getVersion();
400       String s = descriptor.getName() + (version != null ? " (" + version + ")" : "");
401       if (descriptor.isEnabled()) {
402         if (descriptor.isBundled() || SPECIAL_IDEA_PLUGIN.equals(descriptor.getName())) loadedBundled.add(s);
403         else loadedCustom.add(s);
404       }
405       else {
406         disabled.add(s);
407       }
408     }
409
410     Collections.sort(loadedBundled);
411     Collections.sort(loadedCustom);
412     Collections.sort(disabled);
413
414     getLogger().info("Loaded bundled plugins: " + StringUtil.join(loadedBundled, ", "));
415     if (!loadedCustom.isEmpty()) {
416       getLogger().info("Loaded custom plugins: " + StringUtil.join(loadedCustom, ", "));
417     }
418     if (!disabled.isEmpty()) {
419       getLogger().info("Disabled plugins: " + StringUtil.join(disabled, ", "));
420     }
421   }
422
423   static ClassLoader[] getParentLoaders(Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap, PluginId[] pluginIds) {
424     if (isUnitTestMode()) return new ClassLoader[0];
425     final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
426     for (final PluginId id : pluginIds) {
427       IdeaPluginDescriptor pluginDescriptor = idToDescriptorMap.get(id);
428       if (pluginDescriptor == null) {
429         continue; // Might be an optional dependency
430       }
431
432       final ClassLoader loader = pluginDescriptor.getPluginClassLoader();
433       if (loader == null) {
434         getLogger().error("Plugin class loader should be initialized for plugin " + id);
435       }
436       classLoaders.add(loader);
437     }
438     return classLoaders.toArray(new ClassLoader[classLoaders.size()]);
439   }
440
441   static int countPlugins(String pluginsPath) {
442     File configuredPluginsDir = new File(pluginsPath);
443     if (configuredPluginsDir.exists()) {
444       String[] list = configuredPluginsDir.list();
445       if (list != null) {
446         return list.length;
447       }
448     }
449     return 0;
450   }
451
452   static Collection<URL> getClassLoaderUrls() {
453     final ClassLoader classLoader = PluginManagerCore.class.getClassLoader();
454     final Class<? extends ClassLoader> aClass = classLoader.getClass();
455     try {
456       @SuppressWarnings("unchecked") List<URL> urls = (List<URL>)aClass.getMethod("getUrls").invoke(classLoader);
457       return urls;
458     }
459     catch (IllegalAccessException ignored) { }
460     catch (InvocationTargetException ignored) { }
461     catch (NoSuchMethodException ignored) { }
462
463     if (classLoader instanceof URLClassLoader) {
464       return Arrays.asList(((URLClassLoader)classLoader).getURLs());
465     }
466
467     return Collections.emptyList();
468   }
469
470   static void prepareLoadingPluginsErrorMessage(final String errorMessage) {
471     if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
472       if (ApplicationManager.getApplication() != null
473           && !ApplicationManager.getApplication().isHeadlessEnvironment()
474           && !ApplicationManager.getApplication().isUnitTestMode()) {
475         if (myPluginError == null) {
476           myPluginError = errorMessage;
477         }
478         else {
479           myPluginError += "\n" + errorMessage;
480         }
481       } else {
482         getLogger().error(errorMessage);
483       }
484     }
485   }
486
487   private static void addModulesAsDependents(Map<PluginId, ? super IdeaPluginDescriptorImpl> map) {
488     for (Map.Entry<String, IdeaPluginDescriptorImpl> entry : ourModulesToContainingPlugins.entrySet()) {
489       map.put(PluginId.getId(entry.getKey()), entry.getValue());
490     }
491   }
492
493   static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
494     final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
495     final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
496     if (!builder.isAcyclic()) {
497       builder.getSCCs().forEach(new TIntProcedure() {
498         int myTNumber = 0;
499         @Override
500         public boolean execute(int size) {
501           if (size > 1) {
502             for (int j = 0; j < size; j++) {
503               idToDescriptorMap.get(builder.getNodeByTNumber(myTNumber + j)).setEnabled(false);
504             }
505           }
506           myTNumber += size;
507           return true;
508         }
509       });
510     }
511
512     final Comparator<PluginId> idComparator = builder.comparator();
513     return new Comparator<IdeaPluginDescriptor>() {
514       @Override
515       public int compare(@NotNull IdeaPluginDescriptor o1, @NotNull IdeaPluginDescriptor o2) {
516         final PluginId pluginId1 = o1.getPluginId();
517         final PluginId pluginId2 = o2.getPluginId();
518         if (pluginId1.getIdString().equals(CORE_PLUGIN_ID)) return -1;
519         if (pluginId2.getIdString().equals(CORE_PLUGIN_ID)) return 1;
520         return idComparator.compare(pluginId1, pluginId2);
521       }
522     };
523   }
524
525   private static Graph<PluginId> createPluginIdGraph(final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
526     final List<PluginId> ids = new ArrayList<PluginId>(idToDescriptorMap.keySet());
527     // this magic ensures that the dependent plugins always follow their dependencies in lexicographic order
528     // needed to make sure that extensions are always in the same order
529     Collections.sort(ids, new Comparator<PluginId>() {
530       @Override
531       public int compare(@NotNull PluginId o1, @NotNull PluginId o2) {
532         return o2.getIdString().compareTo(o1.getIdString());
533       }
534     });
535     return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<PluginId>() {
536       @Override
537       public Collection<PluginId> getNodes() {
538         return ids;
539       }
540
541       @Override
542       public Iterator<PluginId> getIn(PluginId pluginId) {
543         final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
544         ArrayList<PluginId> plugins = new ArrayList<PluginId>();
545         for (PluginId dependentPluginId : descriptor.getDependentPluginIds()) {
546           // check for missing optional dependency
547           IdeaPluginDescriptor dep = idToDescriptorMap.get(dependentPluginId);
548           if (dep != null) {
549             plugins.add(dep.getPluginId());
550           }
551         }
552         return plugins.iterator();
553       }
554     }));
555   }
556
557   @Nullable
558   static IdeaPluginDescriptorImpl loadDescriptorFromDir(@NotNull File file, @NotNull String fileName) {
559     File descriptorFile = new File(file, META_INF + File.separator + fileName);
560     if (descriptorFile.exists()) {
561       try {
562         IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
563         descriptor.readExternal(descriptorFile.toURI().toURL());
564         return descriptor;
565       }
566       catch (XmlSerializationException e) {
567         getLogger().info("Cannot load " + file, e);
568         prepareLoadingPluginsErrorMessage("File '" + file.getName() + "' contains invalid plugin descriptor.");
569       }
570       catch (Throwable e) {
571         getLogger().info("Cannot load " + file, e);
572       }
573     }
574
575     return null;
576   }
577
578   @Nullable
579   static IdeaPluginDescriptorImpl loadDescriptorFromJar(@NotNull File file, @NotNull String fileName) {
580     try {
581       String fileURL = StringUtil.replace(file.toURI().toASCIIString(), "!", "%21");
582       URL jarURL = new URL("jar:" + fileURL + "!/META-INF/" + fileName);
583
584       ZipFile zipFile = ZipFileCache.acquire(file.getPath());
585       try {
586         ZipEntry entry = zipFile.getEntry("META-INF/" + fileName);
587         if (entry != null) {
588           Document document = JDOMUtil.loadDocument(zipFile.getInputStream(entry));
589           IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
590           descriptor.readExternal(document, jarURL);
591           return descriptor;
592         }
593       }
594       finally {
595         ZipFileCache.release(zipFile);
596       }
597     }
598     catch (XmlSerializationException e) {
599       getLogger().info("Cannot load " + file, e);
600       prepareLoadingPluginsErrorMessage("File '" + file.getName() + "' contains invalid plugin descriptor.");
601     }
602     catch (Throwable e) {
603       getLogger().info("Cannot load " + file, e);
604     }
605
606     return null;
607   }
608
609   @Nullable
610   public static IdeaPluginDescriptorImpl loadDescriptorFromJar(@NotNull File file) {
611     return loadDescriptorFromJar(file, PLUGIN_XML);
612   }
613
614   @Nullable
615   public static IdeaPluginDescriptorImpl loadDescriptor(@NotNull final File file, @NotNull String fileName) {
616     IdeaPluginDescriptorImpl descriptor = null;
617
618     if (file.isDirectory()) {
619       descriptor = loadDescriptorFromDir(file, fileName);
620
621       if (descriptor == null) {
622         File libDir = new File(file, "lib");
623         if (!libDir.isDirectory()) {
624           return null;
625         }
626         final File[] files = libDir.listFiles();
627         if (files == null || files.length == 0) {
628           return null;
629         }
630         Arrays.sort(files, new Comparator<File>() {
631           @Override
632           public int compare(@NotNull File o1, @NotNull File o2) {
633             if (o2.getName().startsWith(file.getName())) return Integer.MAX_VALUE;
634             if (o1.getName().startsWith(file.getName())) return -Integer.MAX_VALUE;
635             if (o2.getName().startsWith("resources")) return -Integer.MAX_VALUE;
636             if (o1.getName().startsWith("resources")) return Integer.MAX_VALUE;
637             return 0;
638           }
639         });
640         for (final File f : files) {
641           if (FileUtil.isJarOrZip(f)) {
642             descriptor = loadDescriptorFromJar(f, fileName);
643             if (descriptor != null) {
644               descriptor.setPath(file);
645               break;
646             }
647             //           getLogger().warn("Cannot load descriptor from " + f.getName() + "");
648           }
649           else if (f.isDirectory()) {
650             IdeaPluginDescriptorImpl descriptor1 = loadDescriptorFromDir(f, fileName);
651             if (descriptor1 != null) {
652               if (descriptor != null) {
653                 getLogger().info("Cannot load " + file + " because two or more plugin.xml's detected");
654                 return null;
655               }
656               descriptor = descriptor1;
657               descriptor.setPath(file);
658             }
659           }
660         }
661       }
662     }
663     else if (StringUtil.endsWithIgnoreCase(file.getName(), ".jar") && file.exists()) {
664       descriptor = loadDescriptorFromJar(file, fileName);
665     }
666
667     if (descriptor != null && descriptor.getOptionalConfigs() != null && !descriptor.getOptionalConfigs().isEmpty()) {
668       final Map<PluginId, IdeaPluginDescriptorImpl> descriptors =
669         new THashMap<PluginId, IdeaPluginDescriptorImpl>(descriptor.getOptionalConfigs().size());
670       for (Map.Entry<PluginId, String> entry : descriptor.getOptionalConfigs().entrySet()) {
671         String optionalDescriptorName = entry.getValue();
672         assert !Comparing.equal(fileName, optionalDescriptorName) : "recursive dependency: " + fileName;
673
674         IdeaPluginDescriptorImpl optionalDescriptor = loadDescriptor(file, optionalDescriptorName);
675         if (optionalDescriptor == null && !FileUtil.isJarOrZip(file)) {
676           for (URL url : getClassLoaderUrls()) {
677             if ("file".equals(url.getProtocol())) {
678               optionalDescriptor = loadDescriptor(new File(decodeUrl(url.getFile())), optionalDescriptorName);
679               if (optionalDescriptor != null) {
680                 break;
681               }
682             }
683           }
684         }
685         if (optionalDescriptor != null) {
686           descriptors.put(entry.getKey(), optionalDescriptor);
687         }
688         else {
689           getLogger().info("Cannot find optional descriptor " + optionalDescriptorName);
690         }
691       }
692       descriptor.setOptionalDescriptors(descriptors);
693     }
694
695     return descriptor;
696   }
697
698   public static void loadDescriptors(String pluginsPath,
699                                      List<IdeaPluginDescriptorImpl> result,
700                                      @Nullable StartupProgress progress,
701                                      int pluginsCount) {
702     loadDescriptors(new File(pluginsPath), result, progress, pluginsCount);
703   }
704
705   public static void loadDescriptors(@NotNull File pluginsHome,
706                                      List<IdeaPluginDescriptorImpl> result,
707                                      @Nullable StartupProgress progress,
708                                      int pluginsCount) {
709     final File[] files = pluginsHome.listFiles();
710     if (files != null) {
711       int i = result.size();
712       for (File file : files) {
713         final IdeaPluginDescriptorImpl descriptor = loadDescriptor(file, PLUGIN_XML);
714         if (descriptor == null) continue;
715         if (progress != null) {
716           progress.showProgress(descriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * ((float)++i / pluginsCount));
717         }
718         int oldIndex = result.indexOf(descriptor);
719         if (oldIndex >= 0) {
720           final IdeaPluginDescriptorImpl oldDescriptor = result.get(oldIndex);
721           if (StringUtil.compareVersionNumbers(oldDescriptor.getVersion(), descriptor.getVersion()) < 0) {
722             result.set(oldIndex, descriptor);
723           }
724         }
725         else {
726           result.add(descriptor);
727         }
728       }
729     }
730   }
731
732   @Nullable
733   static String filterBadPlugins(List<? extends IdeaPluginDescriptor> result, final Map<String, String> disabledPluginNames) {
734     final Map<PluginId, IdeaPluginDescriptor> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptor>();
735     final StringBuilder message = new StringBuilder();
736     boolean pluginsWithoutIdFound = false;
737     for (Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
738       final IdeaPluginDescriptor descriptor = it.next();
739       final PluginId id = descriptor.getPluginId();
740       if (id == null) {
741         pluginsWithoutIdFound = true;
742       }
743       else if (idToDescriptorMap.containsKey(id)) {
744         message.append("<br>");
745         message.append(IdeBundle.message("message.duplicate.plugin.id"));
746         message.append(id);
747         it.remove();
748       }
749       else if (descriptor.isEnabled()) {
750         idToDescriptorMap.put(id, descriptor);
751       }
752     }
753     addModulesAsDependents(idToDescriptorMap);
754     final List<String> disabledPluginIds = new SmartList<String>();
755     final LinkedHashSet<String> faultyDescriptors = new LinkedHashSet<String>();
756     for (final Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
757       final IdeaPluginDescriptor pluginDescriptor = it.next();
758       checkDependants(pluginDescriptor, new Function<PluginId, IdeaPluginDescriptor>() {
759         @Override
760         public IdeaPluginDescriptor fun(final PluginId pluginId) {
761           return idToDescriptorMap.get(pluginId);
762         }
763       }, new Condition<PluginId>() {
764         @Override
765         public boolean value(final PluginId pluginId) {
766           if (!idToDescriptorMap.containsKey(pluginId)) {
767             pluginDescriptor.setEnabled(false);
768             if (!pluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX)) {
769               faultyDescriptors.add(pluginId.getIdString());
770               disabledPluginIds.add(pluginDescriptor.getPluginId().getIdString());
771               message.append("<br>");
772               final String name = pluginDescriptor.getName();
773               final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
774               String pluginName;
775               if (descriptor == null) {
776                 pluginName = pluginId.getIdString();
777                 if (disabledPluginNames.containsKey(pluginName)) {
778                   pluginName = disabledPluginNames.get(pluginName);
779                 }
780               }
781               else {
782                 pluginName = descriptor.getName();
783               }
784
785               message.append(getDisabledPlugins().contains(pluginId.getIdString())
786                              ? IdeBundle.message("error.required.plugin.disabled", name, pluginName)
787                              : IdeBundle.message("error.required.plugin.not.installed", name, pluginName));
788             }
789             it.remove();
790             return false;
791           }
792           return true;
793         }
794       });
795     }
796     if (!disabledPluginIds.isEmpty()) {
797       myPlugins2Disable = disabledPluginIds;
798       myPlugins2Enable = faultyDescriptors;
799       message.append("<br>");
800       message.append("<br>").append("<a href=\"" + DISABLE + "\">Disable ");
801       if (disabledPluginIds.size() == 1) {
802         final PluginId pluginId2Disable = PluginId.getId(disabledPluginIds.iterator().next());
803         message.append(idToDescriptorMap.containsKey(pluginId2Disable) ? idToDescriptorMap.get(pluginId2Disable).getName() : pluginId2Disable.getIdString());
804       }
805       else {
806         message.append("not loaded plugins");
807       }
808       message.append("</a>");
809       boolean possibleToEnable = true;
810       for (String descriptor : faultyDescriptors) {
811         if (disabledPluginNames.get(descriptor) == null) {
812           possibleToEnable = false;
813           break;
814         }
815       }
816       if (possibleToEnable) {
817         message.append("<br>").append("<a href=\"" + ENABLE + "\">Enable ").append(faultyDescriptors.size() == 1 ? disabledPluginNames.get(faultyDescriptors.iterator().next()) : " all necessary plugins").append("</a>");
818       }
819       message.append("<br>").append("<a href=\"" + EDIT + "\">Open plugin manager</a>");
820     }
821     if (pluginsWithoutIdFound) {
822       message.append("<br>");
823       message.append(IdeBundle.message("error.plugins.without.id.found"));
824     }
825     if (message.length() > 0) {
826       message.insert(0, IdeBundle.message("error.problems.found.loading.plugins"));
827       return message.toString();
828     }
829     return "";
830   }
831
832   static void loadDescriptorsFromClassPath(@NotNull List<IdeaPluginDescriptorImpl> result, @Nullable StartupProgress progress) {
833     Collection<URL> urls = getClassLoaderUrls();
834     String platformPrefix = System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY);
835     int i = 0;
836     for (URL url : urls) {
837       i++;
838       if ("file".equals(url.getProtocol())) {
839         File file = new File(decodeUrl(url.getFile()));
840
841         IdeaPluginDescriptorImpl platformPluginDescriptor = null;
842         if (platformPrefix != null) {
843           platformPluginDescriptor = loadDescriptor(file, platformPrefix + "Plugin.xml");
844           if (platformPluginDescriptor != null && !result.contains(platformPluginDescriptor)) {
845             platformPluginDescriptor.setUseCoreClassLoader(true);
846             result.add(platformPluginDescriptor);
847           }
848         }
849
850         IdeaPluginDescriptorImpl pluginDescriptor = loadDescriptor(file, PLUGIN_XML);
851         if (platformPrefix != null && pluginDescriptor != null && pluginDescriptor.getName().equals(SPECIAL_IDEA_PLUGIN)) {
852           continue;
853         }
854         if (pluginDescriptor != null && !result.contains(pluginDescriptor)) {
855           if (platformPluginDescriptor != null) {
856             // if we found a regular plugin.xml in the same .jar/root as a platform-prefixed descriptor, use the core loader for it too
857             pluginDescriptor.setUseCoreClassLoader(true);
858           }
859           result.add(pluginDescriptor);
860           if (progress != null) {
861             progress.showProgress("Plugin loaded: " + pluginDescriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * ((float)i / urls.size()));
862           }
863         }
864       }
865     }
866   }
867
868   @SuppressWarnings("deprecation")
869   private static String decodeUrl(String file) {
870     String quotePluses = StringUtil.replace(file, "+", "%2B");
871     return URLDecoder.decode(quotePluses);
872   }
873
874   static void loadDescriptorsFromProperty(final List<IdeaPluginDescriptorImpl> result) {
875     final String pathProperty = System.getProperty(PROPERTY_PLUGIN_PATH);
876     if (pathProperty == null) return;
877
878     for (StringTokenizer t = new StringTokenizer(pathProperty, File.pathSeparator + ","); t.hasMoreTokens();) {
879       String s = t.nextToken();
880       final IdeaPluginDescriptorImpl ideaPluginDescriptor = loadDescriptor(new File(s), PLUGIN_XML);
881       if (ideaPluginDescriptor != null) {
882         result.add(ideaPluginDescriptor);
883       }
884     }
885   }
886
887   public static IdeaPluginDescriptorImpl[] loadDescriptors(@Nullable StartupProgress progress) {
888     if (ClassUtilCore.isLoadingOfExternalPluginsDisabled()) {
889       return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
890     }
891
892     final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
893
894     int pluginsCount = countPlugins(PathManager.getPluginsPath()) + countPlugins(PathManager.getPreInstalledPluginsPath());
895     loadDescriptors(PathManager.getPluginsPath(), result, progress, pluginsCount);
896     Application application = ApplicationManager.getApplication();
897     boolean fromSources = false;
898     if (application == null || !application.isUnitTestMode()) {
899       int size = result.size();
900       loadDescriptors(PathManager.getPreInstalledPluginsPath(), result, progress, pluginsCount);
901       fromSources = size == result.size();
902     }
903
904     loadDescriptorsFromProperty(result);
905
906     loadDescriptorsFromClassPath(result, fromSources ? progress : null);
907
908     IdeaPluginDescriptorImpl[] pluginDescriptors = result.toArray(new IdeaPluginDescriptorImpl[result.size()]);
909     final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptorImpl>();
910     for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
911       idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
912     }
913
914     Arrays.sort(pluginDescriptors, getPluginDescriptorComparator(idToDescriptorMap));
915     return pluginDescriptors;
916   }
917
918   static void mergeOptionalConfigs(Map<PluginId, IdeaPluginDescriptorImpl> descriptors) {
919     final Map<PluginId, IdeaPluginDescriptorImpl> descriptorsWithModules = new THashMap<PluginId, IdeaPluginDescriptorImpl>(descriptors);
920     addModulesAsDependents(descriptorsWithModules);
921     for (IdeaPluginDescriptorImpl descriptor : descriptors.values()) {
922       final Map<PluginId, IdeaPluginDescriptorImpl> optionalDescriptors = descriptor.getOptionalDescriptors();
923       if (optionalDescriptors != null && !optionalDescriptors.isEmpty()) {
924         for (Map.Entry<PluginId, IdeaPluginDescriptorImpl> entry: optionalDescriptors.entrySet()) {
925           if (descriptorsWithModules.containsKey(entry.getKey())) {
926             descriptor.mergeOptionalConfig(entry.getValue());
927           }
928         }
929       }
930     }
931   }
932
933   public static void initClassLoader(@NotNull ClassLoader parentLoader, @NotNull IdeaPluginDescriptorImpl descriptor) {
934     final List<File> classPath = descriptor.getClassPath();
935     final ClassLoader loader =
936         createPluginClassLoader(classPath.toArray(new File[classPath.size()]), new ClassLoader[]{parentLoader}, descriptor);
937     descriptor.setLoader(loader);
938   }
939
940   static BuildNumber getBuildNumber() {
941     if (ourBuildNumber == null) {
942       ourBuildNumber = BuildNumber.fromString(System.getProperty("idea.plugins.compatible.build"));
943       if (ourBuildNumber == null) {
944         ourBuildNumber = BUILD_NUMBER == null ? null : BuildNumber.fromString(BUILD_NUMBER);
945         if (ourBuildNumber == null) {
946           ourBuildNumber = BuildNumber.fallback();
947         }
948       }
949     }
950     return ourBuildNumber;
951   }
952
953   static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor, IdeaPluginDescriptor[] loaded) {
954     final String idString = descriptor.getPluginId().getIdString();
955     if (CORE_PLUGIN_ID.equals(idString)) {
956       return false;
957     }
958
959     //noinspection HardCodedStringLiteral
960     final String pluginId = System.getProperty("idea.load.plugins.id");
961     if (pluginId == null) {
962       if (descriptor instanceof IdeaPluginDescriptorImpl && !descriptor.isEnabled()) return true;
963
964       if (!shouldLoadPlugins()) return true;
965     }
966     final List<String> pluginIds = pluginId == null ? null : StringUtil.split(pluginId, ",");
967
968     final boolean checkModuleDependencies = !ourModulesToContainingPlugins.isEmpty() && !ourModulesToContainingPlugins.containsKey("com.intellij.modules.all");
969     if (checkModuleDependencies && !hasModuleDependencies(descriptor)) {
970       return true;
971     }
972
973     boolean shouldLoad;
974     //noinspection HardCodedStringLiteral
975     final String loadPluginCategory = System.getProperty("idea.load.plugins.category");
976     if (loadPluginCategory != null) {
977       shouldLoad = loadPluginCategory.equals(descriptor.getCategory());
978     }
979     else {
980       if (pluginIds != null) {
981         shouldLoad = pluginIds.contains(idString);
982         if (!shouldLoad) {
983           Map<PluginId,IdeaPluginDescriptor> map = new THashMap<PluginId, IdeaPluginDescriptor>();
984           for (IdeaPluginDescriptor pluginDescriptor : loaded) {
985             map.put(pluginDescriptor.getPluginId(), pluginDescriptor);
986           }
987           addModulesAsDependents(map);
988           for (String id : pluginIds) {
989             final IdeaPluginDescriptor descriptorFromProperty = map.get(PluginId.getId(id));
990             if (descriptorFromProperty != null && isDependent(descriptorFromProperty, descriptor.getPluginId(), map, checkModuleDependencies)) {
991               shouldLoad = true;
992               break;
993             }
994           }
995         }
996       } else {
997         shouldLoad = !getDisabledPlugins().contains(idString);
998       }
999       if (shouldLoad && descriptor instanceof IdeaPluginDescriptorImpl) {
1000         if (isIncompatible(descriptor)) return true;
1001       }
1002     }
1003
1004     return !shouldLoad;
1005   }
1006
1007   public static boolean isIncompatible(final IdeaPluginDescriptor descriptor) {
1008     return isIncompatible(descriptor, getBuildNumber());
1009   }
1010
1011   public static boolean isIncompatible(final IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
1012
1013     if (buildNumber == null) {
1014       buildNumber = getBuildNumber();
1015     }
1016
1017     try {
1018       if (!StringUtil.isEmpty(descriptor.getSinceBuild())) {
1019         BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild(), descriptor.getName());
1020         if (sinceBuild.compareTo(buildNumber) > 0) {
1021           return true;
1022         }
1023       }
1024
1025       if (!StringUtil.isEmpty(descriptor.getUntilBuild()) && !buildNumber.isSnapshot()) {
1026         BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild(), descriptor.getName());
1027         if (untilBuild.compareTo(buildNumber) < 0) {
1028           return true;
1029         }
1030       }
1031     }
1032     catch (RuntimeException ignored) { }
1033
1034     return false;
1035   }
1036
1037   public static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor) {
1038     if (descriptor instanceof IdeaPluginDescriptorImpl) {
1039       IdeaPluginDescriptorImpl descriptorImpl = (IdeaPluginDescriptorImpl)descriptor;
1040       Boolean skipped = descriptorImpl.getSkipped();
1041       if (skipped != null) {
1042         return skipped.booleanValue();
1043       }
1044       boolean result = shouldSkipPlugin(descriptor, ourPlugins) || isBrokenPlugin(descriptor);
1045       descriptorImpl.setSkipped(result);
1046       return result;
1047     }
1048     return shouldSkipPlugin(descriptor, ourPlugins) || isBrokenPlugin(descriptor);
1049   }
1050
1051   static void initializePlugins(@Nullable StartupProgress progress) {
1052     configureExtensions();
1053
1054     final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress);
1055
1056     final Class callerClass = ReflectionUtil.findCallerClass(1);
1057     assert callerClass != null;
1058     final ClassLoader parentLoader = callerClass.getClassLoader();
1059
1060     final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
1061     final Map<String, String> disabledPluginNames = new THashMap<String, String>();
1062     List<String> brokenPluginsList = new SmartList<String>();
1063     for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
1064       boolean skipped = shouldSkipPlugin(descriptor, pluginDescriptors);
1065       if (!skipped) {
1066         if (isBrokenPlugin(descriptor)) {
1067           brokenPluginsList.add(descriptor.getName());
1068           skipped = true;
1069         }
1070       }
1071
1072       if (!skipped) {
1073         final List<String> modules = descriptor.getModules();
1074         if (modules != null) {
1075           for (String module : modules) {
1076             if (!ourModulesToContainingPlugins.containsKey(module)) {
1077               ourModulesToContainingPlugins.put(module, descriptor);
1078             }
1079           }
1080         }
1081         result.add(descriptor);
1082       }
1083       else {
1084         descriptor.setEnabled(false);
1085         disabledPluginNames.put(descriptor.getPluginId().getIdString(), descriptor.getName());
1086         initClassLoader(parentLoader, descriptor);
1087       }
1088     }
1089
1090     String errorMessage = filterBadPlugins(result, disabledPluginNames);
1091
1092     if (!brokenPluginsList.isEmpty()) {
1093       if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
1094         errorMessage += "<br>";
1095       }
1096       errorMessage += "Following plugins are incompatible with current IDE build: " + StringUtil.join(brokenPluginsList, ", ")
1097                       + "<br>\n" + StringUtil.notNullize(errorMessage);
1098     }
1099
1100     final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptorImpl>();
1101     for (IdeaPluginDescriptorImpl descriptor : result) {
1102       idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
1103     }
1104
1105     final IdeaPluginDescriptor corePluginDescriptor = idToDescriptorMap.get(PluginId.getId(CORE_PLUGIN_ID));
1106     assert corePluginDescriptor != null : CORE_PLUGIN_ID + " not found; platform prefix is " + System.getProperty(PlatformUtils.PLATFORM_PREFIX_KEY);
1107     for (IdeaPluginDescriptorImpl descriptor : result) {
1108       if (descriptor != corePluginDescriptor) {
1109         descriptor.insertDependency(corePluginDescriptor);
1110       }
1111     }
1112
1113     mergeOptionalConfigs(idToDescriptorMap);
1114     addModulesAsDependents(idToDescriptorMap);
1115
1116     final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
1117     final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
1118     if (!builder.isAcyclic()) {
1119       if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
1120         errorMessage += "<br>";
1121       }
1122
1123       final String cyclePresentation;
1124       if (ApplicationManager.getApplication().isInternal()) {
1125         final List<String> cycles = new ArrayList<String>();
1126         builder.getSCCs().forEach(new TIntProcedure() {
1127           int myTNumber = 0;
1128           @Override
1129           public boolean execute(int size) {
1130             if (size > 1) {
1131               String cycle = "";
1132               for (int j = 0; j < size; j++) {
1133                 cycle += builder.getNodeByTNumber(myTNumber + j).getIdString() + " ";
1134               }
1135               cycles.add(cycle);
1136             }
1137             myTNumber += size;
1138             return true;
1139           }
1140         });
1141         cyclePresentation = ": " + StringUtil.join(cycles, ";");
1142       } else {
1143         final Couple<PluginId> circularDependency = builder.getCircularDependency();
1144         final PluginId id = circularDependency.getFirst();
1145         final PluginId parentId = circularDependency.getSecond();
1146         cyclePresentation = id + "->" + parentId + "->...->" + id;
1147       }
1148       errorMessage += IdeBundle.message("error.plugins.should.not.have.cyclic.dependencies") + cyclePresentation;
1149     }
1150
1151     prepareLoadingPluginsErrorMessage(errorMessage);
1152
1153     final Comparator<PluginId> idComparator = builder.comparator();
1154     // sort descriptors according to plugin dependencies
1155     Collections.sort(result, new Comparator<IdeaPluginDescriptor>() {
1156       @Override
1157       public int compare(@NotNull IdeaPluginDescriptor o1, @NotNull IdeaPluginDescriptor o2) {
1158         return idComparator.compare(o1.getPluginId(), o2.getPluginId());
1159       }
1160     });
1161
1162     for (int i = 0; i < result.size(); i++) {
1163       ourId2Index.put(result.get(i).getPluginId(), i);
1164     }
1165
1166     int i = 0;
1167     for (final IdeaPluginDescriptorImpl pluginDescriptor : result) {
1168       if (pluginDescriptor.getPluginId().getIdString().equals(CORE_PLUGIN_ID) || pluginDescriptor.isUseCoreClassLoader()) {
1169         pluginDescriptor.setLoader(parentLoader);
1170       }
1171       else {
1172         final List<File> classPath = pluginDescriptor.getClassPath();
1173         final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
1174         final ClassLoader[] parentLoaders = getParentLoaders(idToDescriptorMap, dependentPluginIds);
1175
1176         final ClassLoader pluginClassLoader = createPluginClassLoader(classPath.toArray(new File[classPath.size()]),
1177                                                                       parentLoaders.length > 0 ? parentLoaders : new ClassLoader[] {parentLoader},
1178                                                                       pluginDescriptor);
1179         pluginDescriptor.setLoader(pluginClassLoader);
1180       }
1181
1182       if (progress != null) {
1183         progress.showProgress("", PLUGINS_PROGRESS_MAX_VALUE + (i++ / (float)result.size()) * 0.35f);
1184       }
1185     }
1186
1187     registerExtensionPointsAndExtensions(Extensions.getRootArea(), result);
1188     Extensions.getRootArea().getExtensionPoint(Extensions.AREA_LISTENER_EXTENSION_POINT).registerExtension(new AreaListener() {
1189       @Override
1190       public void areaCreated(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
1191         registerExtensionPointsAndExtensions(Extensions.getArea(areaInstance), result);
1192       }
1193
1194       @Override
1195       public void areaDisposing(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
1196       }
1197     });
1198
1199
1200     ourPlugins = pluginDescriptors;
1201   }
1202
1203   private static void registerExtensionPointsAndExtensions(ExtensionsArea area, List<IdeaPluginDescriptorImpl> loadedPlugins) {
1204     for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
1205       descriptor.registerExtensionPoints(area);
1206     }
1207
1208     ExtensionPoint[] extensionPoints = area.getExtensionPoints();
1209     Set<String> epNames = new THashSet<String>(extensionPoints.length);
1210     for (ExtensionPoint point : extensionPoints) {
1211       epNames.add(point.getName());
1212     }
1213
1214     for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
1215       for (String epName : epNames) {
1216         descriptor.registerExtensions(area, epName);
1217       }
1218     }
1219   }
1220
1221   public static void initPlugins(@Nullable StartupProgress progress) {
1222     long start = System.currentTimeMillis();
1223     try {
1224       initializePlugins(progress);
1225     }
1226     catch (RuntimeException e) {
1227       getLogger().error(e);
1228       throw e;
1229     }
1230     getLogger().info(ourPlugins.length + " plugins initialized in " + (System.currentTimeMillis() - start) + " ms");
1231     logPlugins();
1232     ClassUtilCore.clearJarURLCache();
1233   }
1234
1235   private static class LoggerHolder {
1236     private static final Logger ourLogger = Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
1237   }
1238
1239   private static class IdeaLogProvider implements LogProvider {
1240     @Override
1241     public void error(String message) {
1242       getLogger().error(message);
1243     }
1244
1245     @Override
1246     public void error(String message, Throwable t) {
1247       getLogger().error(message, t);
1248     }
1249
1250     @Override
1251     public void error(Throwable t) {
1252       getLogger().error(t);
1253     }
1254
1255     @Override
1256     public void warn(String message) {
1257       getLogger().info(message);
1258     }
1259
1260     @Override
1261     public void warn(String message, Throwable t) {
1262       getLogger().info(message, t);
1263     }
1264
1265     @Override
1266     public void warn(Throwable t) {
1267       getLogger().info(t);
1268     }
1269   }
1270 }