'ruby' and 'python' modules included into Ruby/Python plugins to allow writing plugin...
[idea/community.git] / platform / core-impl / src / com / intellij / ide / plugins / PluginManagerCore.java
1 /*
2  * Copyright 2000-2013 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.text.StringUtil;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.Function;
34 import com.intellij.util.PlatformUtilsCore;
35 import com.intellij.util.ReflectionUtil;
36 import com.intellij.util.containers.ContainerUtil;
37 import com.intellij.util.containers.MultiMap;
38 import com.intellij.util.execution.ParametersListUtil;
39 import com.intellij.util.graph.CachingSemiGraph;
40 import com.intellij.util.graph.DFSTBuilder;
41 import com.intellij.util.graph.Graph;
42 import com.intellij.util.graph.GraphGenerator;
43 import com.intellij.util.lang.JarMemoryLoader;
44 import com.intellij.util.xmlb.XmlSerializationException;
45 import gnu.trove.THashMap;
46 import org.jdom.Document;
47 import org.jetbrains.annotations.NonNls;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.io.*;
52 import java.lang.reflect.InvocationTargetException;
53 import java.lang.reflect.Method;
54 import java.net.*;
55 import java.util.*;
56 import java.util.zip.ZipEntry;
57 import java.util.zip.ZipInputStream;
58
59 public class PluginManagerCore {
60   @NonNls public static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
61   @NonNls public static final String CORE_PLUGIN_ID = "com.intellij";
62   @NonNls public static final String META_INF = "META-INF";
63   @NonNls public static final String PLUGIN_XML = "plugin.xml";
64   public static final float PLUGINS_PROGRESS_MAX_VALUE = 0.3f;
65   static final Map<PluginId,Integer> ourId2Index = new THashMap<PluginId, Integer>();
66   @NonNls static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module";
67   static final List<String> ourAvailableModules = new ArrayList<String>();
68   static final PluginClassCache ourPluginClasses = new PluginClassCache();
69   @NonNls static final String SPECIAL_IDEA_PLUGIN = "IDEA CORE";
70   static final String DISABLE = "disable";
71   static final String ENABLE = "enable";
72   static final String EDIT = "edit";
73   @NonNls private static final String PROPERTY_PLUGIN_PATH = "plugin.path";
74   static List<String> ourDisabledPlugins = null;
75   static MultiMap<String, String> ourDisabledPluginVersions = null;
76   static IdeaPluginDescriptor[] ourPlugins;
77   static String myPluginError = null;
78   static List<String> myPlugins2Disable = null;
79   static LinkedHashSet<String> myPlugins2Enable = null;
80   public static String BUILD_NUMBER;
81   private static BuildNumber ourBuildNumber;
82
83   /**
84    * do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by IdeaClassLoader
85    */
86   public static synchronized IdeaPluginDescriptor[] getPlugins() {
87     if (ourPlugins == null) {
88       initPlugins(null);
89     }
90     return ourPlugins;
91   }
92
93   public static void loadDisabledPlugins(final String configPath, final Collection<String> disabledPlugins) {
94     final File file = new File(configPath, DISABLED_PLUGINS_FILENAME);
95     if (file.isFile()) {
96       try {
97         BufferedReader reader = new BufferedReader(new FileReader(file));
98         try {
99           String id;
100           while ((id = reader.readLine()) != null) {
101             disabledPlugins.add(id.trim());
102           }
103         }
104         finally {
105           reader.close();
106         }
107       }
108       catch (IOException ignored) { }
109     }
110   }
111
112   @NotNull
113   public static List<String> getDisabledPlugins() {
114     if (ourDisabledPlugins == null) {
115       ourDisabledPlugins = new ArrayList<String>();
116       if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
117         loadDisabledPlugins(PathManager.getConfigPath(), ourDisabledPlugins);
118       }
119     }
120     return ourDisabledPlugins;
121   }
122
123   public static MultiMap<String, String> getDisabledPluginVersions() {
124     if (ourDisabledPluginVersions == null) {
125       ourDisabledPluginVersions = MultiMap.createSet();
126
127       if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
128         BufferedReader br = new BufferedReader(new InputStreamReader(PluginManagerCore.class.getResourceAsStream("/brokenPlugins.txt")));
129         try {
130           String s;
131           while ((s = br.readLine()) != null) {
132             s = s.trim();
133             if (s.startsWith("//")) continue;
134
135             List<String> tokens = ParametersListUtil.parse(s);
136             if (tokens.isEmpty()) continue;
137
138             if (tokens.size() == 1) {
139               throw new RuntimeException("brokenPlugins.txt is broken. The line contains plugin name, but does not contains version: " + s);
140             }
141
142             String pluginId = tokens.get(0);
143             List<String> versions = tokens.subList(1, tokens.size());
144
145             ourDisabledPluginVersions.putValues(pluginId, versions);
146           }
147         }
148         catch (IOException e) {
149           throw new RuntimeException("Failed to read /brokenPlugins.txt", e);
150         }
151         finally {
152           StreamUtil.closeStream(br);
153         }
154       }
155     }
156     return ourDisabledPluginVersions;
157   }
158
159   static boolean isUnitTestMode() {
160     final Application app = ApplicationManager.getApplication();
161     return app != null && app.isUnitTestMode();
162   }
163
164   public static void savePluginsList(Collection<String> ids, boolean append, File plugins) throws IOException {
165     if (!plugins.isFile()) {
166       FileUtil.ensureCanCreateFile(plugins);
167     }
168     PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins, append)));
169     try {
170       for (String id : ids) {
171         printWriter.println(id);
172       }
173       printWriter.flush();
174     }
175     finally {
176       printWriter.close();
177     }
178   }
179
180   public static boolean disablePlugin(String id) {
181     if (getDisabledPlugins().contains(id)) return false;
182     getDisabledPlugins().add(id);
183     try {
184       saveDisabledPlugins(getDisabledPlugins(), false);
185     }
186     catch (IOException e) {
187       return false;
188     }
189     return true;
190   }
191
192   public static boolean enablePlugin(String id) {
193     if (!getDisabledPlugins().contains(id)) return false;
194     getDisabledPlugins().remove(id);
195     try {
196       saveDisabledPlugins(getDisabledPlugins(), false);
197     }
198     catch (IOException e) {
199       return false;
200     }
201     return true;
202   }
203
204   public static void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException {
205     File plugins = new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME);
206     savePluginsList(ids, append, plugins);
207     ourDisabledPlugins = null;
208   }
209
210   public static Logger getLogger() {
211     return LoggerHolder.ourLogger;
212   }
213
214   public static int getPluginLoadingOrder(PluginId id) {
215     return ourId2Index.get(id);
216   }
217
218   public static boolean isModuleDependency(final PluginId dependentPluginId) {
219     return dependentPluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX);
220   }
221
222   public static void checkDependants(final IdeaPluginDescriptor pluginDescriptor,
223                                      final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
224                                      final Condition<PluginId> check) {
225     checkDependants(pluginDescriptor, pluginId2Descriptor, check, new HashSet<PluginId>());
226   }
227
228   private static boolean checkDependants(final IdeaPluginDescriptor pluginDescriptor,
229                                          final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
230                                          final Condition<PluginId> check,
231                                          final Set<PluginId> processed) {
232     processed.add(pluginDescriptor.getPluginId());
233     final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
234     final Set<PluginId> optionalDependencies = new HashSet<PluginId>(Arrays.asList(pluginDescriptor.getOptionalDependentPluginIds()));
235     for (final PluginId dependentPluginId : dependentPluginIds) {
236       if (processed.contains(dependentPluginId)) continue;
237
238       // TODO[yole] should this condition be a parameter?
239       if (isModuleDependency(dependentPluginId) && (ourAvailableModules.isEmpty() || ourAvailableModules.contains(dependentPluginId.getIdString()))) {
240         continue;
241       }
242       if (!optionalDependencies.contains(dependentPluginId)) {
243         if (!check.value(dependentPluginId)) {
244           return false;
245         }
246         final IdeaPluginDescriptor dependantPluginDescriptor = pluginId2Descriptor.fun(dependentPluginId);
247         if (dependantPluginDescriptor != null && !checkDependants(dependantPluginDescriptor, pluginId2Descriptor, check, processed)) {
248           return false;
249         }
250       }
251     }
252     return true;
253   }
254
255   public static void addPluginClass(@NotNull String className, PluginId pluginId, boolean loaded) {
256     ourPluginClasses.addPluginClass(className, pluginId, loaded);
257   }
258
259   @Nullable
260   public static PluginId getPluginByClassName(@NotNull String className) {
261     return ourPluginClasses.getPluginByClassName(className);
262   }
263
264   public static void dumpPluginClassStatistics() {
265     ourPluginClasses.dumpPluginClassStatistics();
266   }
267
268   static boolean isDependent(final IdeaPluginDescriptor descriptor,
269                              final PluginId on,
270                              Map<PluginId, IdeaPluginDescriptor> map,
271                              final boolean checkModuleDependencies) {
272     for (PluginId id: descriptor.getDependentPluginIds()) {
273       if (ArrayUtil.contains(id, (Object[])descriptor.getOptionalDependentPluginIds())) {
274         continue;
275       }
276       if (!checkModuleDependencies && isModuleDependency(id)) {
277         continue;
278       }
279       if (id.equals(on)) {
280         return true;
281       }
282       final IdeaPluginDescriptor depDescriptor = map.get(id);
283       if (depDescriptor != null && isDependent(depDescriptor, on, map, checkModuleDependencies)) {
284         return true;
285       }
286     }
287     return false;
288   }
289
290   static boolean hasModuleDependencies(final IdeaPluginDescriptor descriptor) {
291     final PluginId[] dependentPluginIds = descriptor.getDependentPluginIds();
292     for (PluginId dependentPluginId : dependentPluginIds) {
293       if (isModuleDependency(dependentPluginId)) {
294         return true;
295       }
296     }
297     return false;
298   }
299
300   static boolean shouldLoadPlugins() {
301     try {
302       // no plugins during bootstrap
303       Class.forName("com.intellij.openapi.extensions.Extensions");
304     }
305     catch (ClassNotFoundException e) {
306       return false;
307     }
308     //noinspection HardCodedStringLiteral
309     final String loadPlugins = System.getProperty("idea.load.plugins");
310     return loadPlugins == null || Boolean.TRUE.toString().equals(loadPlugins);
311   }
312
313   static void configureExtensions() {
314     Extensions.setLogProvider(new IdeaLogProvider());
315     Extensions.registerAreaClass(ExtensionAreas.IDEA_PROJECT, null);
316     Extensions.registerAreaClass(ExtensionAreas.IDEA_MODULE, ExtensionAreas.IDEA_PROJECT);
317   }
318
319   @SuppressWarnings({"HardCodedStringLiteral"})
320   static Method getAddUrlMethod(final ClassLoader loader) throws NoSuchMethodException {
321     if (loader instanceof URLClassLoader) {
322       final Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
323       addUrlMethod.setAccessible(true);
324       return addUrlMethod;
325     }
326
327     return loader.getClass().getDeclaredMethod("addURL", URL.class);
328   }
329
330   @Nullable
331   static ClassLoader createPluginClassLoader(@NotNull File[] classPath,
332                                              @NotNull ClassLoader[] parentLoaders,
333                                              @NotNull IdeaPluginDescriptor pluginDescriptor) {
334
335     if (pluginDescriptor.getUseIdeaClassLoader()) {
336       try {
337         final ClassLoader loader = PluginManagerCore.class.getClassLoader();
338         final Method addUrlMethod = getAddUrlMethod(loader);
339
340
341         for (File aClassPath : classPath) {
342           final File file = aClassPath.getCanonicalFile();
343           addUrlMethod.invoke(loader, file.toURI().toURL());
344         }
345
346         return loader;
347       }
348       catch (NoSuchMethodException e) {
349         e.printStackTrace();
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 (errorMessage != null) {
472       if (!ApplicationManager.getApplication().isHeadlessEnvironment() && !ApplicationManager.getApplication().isUnitTestMode()) {
473         if (myPluginError == null) {
474           myPluginError = errorMessage;
475         }
476         else {
477           myPluginError += "\n" + errorMessage;
478         }
479       } else {
480         getLogger().error(errorMessage);
481       }
482     }
483   }
484
485   private static void addModulesAsDependents(Map<PluginId, ? super IdeaPluginDescriptorImpl> map) {
486     for (String module : ourAvailableModules) {
487       // fake plugin descriptors to satisfy dependencies
488       map.put(PluginId.getId(module), new IdeaPluginDescriptorImpl());
489     }
490   }
491
492   static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap) {
493     final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
494     final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
495     /*
496     if (!builder.isAcyclic()) {
497       final Pair<String,String> circularDependency = builder.getCircularDependency();
498       throw new Exception("Cyclic dependencies between plugins are not allowed: \"" + circularDependency.getFirst() + "\" and \"" + circularDependency.getSecond() + "");
499     }
500     */
501     final Comparator<PluginId> idComparator = builder.comparator();
502     return new Comparator<IdeaPluginDescriptor>() {
503       @Override
504       public int compare(IdeaPluginDescriptor o1, IdeaPluginDescriptor o2) {
505         return idComparator.compare(o1.getPluginId(), o2.getPluginId());
506       }
507     };
508   }
509
510   private static Graph<PluginId> createPluginIdGraph(final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap) {
511     final List<PluginId> ids = new ArrayList<PluginId>(idToDescriptorMap.keySet());
512     // this magic ensures that the dependent plugins always follow their dependencies in lexicographic order
513     // needed to make sure that extensions are always in the same order
514     Collections.sort(ids, new Comparator<PluginId>() {
515       @Override
516       public int compare(PluginId o1, PluginId o2) {
517         return o2.getIdString().compareTo(o1.getIdString());
518       }
519     });
520     return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<PluginId>() {
521       @Override
522       public Collection<PluginId> getNodes() {
523         return ids;
524       }
525
526       @Override
527       public Iterator<PluginId> getIn(PluginId pluginId) {
528         final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
529         ArrayList<PluginId> plugins = new ArrayList<PluginId>();
530         for (PluginId dependentPluginId : descriptor.getDependentPluginIds()) {
531           // check for missing optional dependency
532           if (idToDescriptorMap.containsKey(dependentPluginId)) {
533             plugins.add(dependentPluginId);
534           }
535         }
536         return plugins.iterator();
537       }
538     }));
539   }
540
541   static IdeaPluginDescriptorImpl[] findCorePlugin(IdeaPluginDescriptorImpl[] pluginDescriptors) {
542     for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
543       if (CORE_PLUGIN_ID.equals(descriptor.getPluginId().getIdString())) {
544         return new IdeaPluginDescriptorImpl[] {descriptor};
545       }
546     }
547     return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
548   }
549
550   @Nullable
551   static IdeaPluginDescriptorImpl loadDescriptorFromDir(final File file, @NonNls String fileName) {
552     IdeaPluginDescriptorImpl descriptor = null;
553     File descriptorFile = new File(file, META_INF + File.separator + fileName);
554     if (descriptorFile.exists()) {
555       descriptor = new IdeaPluginDescriptorImpl(file);
556
557       try {
558         descriptor.readExternal(descriptorFile.toURI().toURL());
559       }
560       catch (Exception e) {
561         System.err.println("Cannot load: " + descriptorFile.getAbsolutePath());
562         e.printStackTrace();
563       }
564     }
565     return descriptor;
566   }
567
568   @Nullable
569   static IdeaPluginDescriptorImpl loadDescriptorFromJar(File file, @NonNls String fileName) {
570     try {
571       URI fileURL = file.toURI();
572       URL jarURL = new URL(
573         "jar:" + StringUtil.replace(fileURL.toASCIIString(), "!", "%21") + "!/META-INF/" + fileName
574       );
575
576       IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
577       FileInputStream in = new FileInputStream(file);
578       ZipInputStream zipStream = new ZipInputStream(in);
579       try {
580         ZipEntry entry = zipStream.getNextEntry();
581         if (entry.getName().equals(JarMemoryLoader.SIZE_ENTRY)) {
582           entry = zipStream.getNextEntry();
583           if (entry.getName().equals("META-INF/" + fileName)) {
584             byte[] content = FileUtil.loadBytes(zipStream, (int)entry.getSize());
585             Document document = JDOMUtil.loadDocument(new ByteArrayInputStream(content));
586             descriptor.readExternal(document, jarURL);
587             return descriptor;
588           }
589         }
590       }
591       finally {
592         zipStream.close();
593         in.close();
594       }
595
596       descriptor.readExternal(jarURL);
597       return descriptor;
598     }
599     catch (XmlSerializationException e) {
600       getLogger().info("Cannot load " + file, e);
601       prepareLoadingPluginsErrorMessage("Plugin file " + file.getName() + " contains invalid plugin descriptor file.");
602     }
603     catch (FileNotFoundException e) {
604       return null;
605     }
606     catch (Exception e) {
607       getLogger().info("Cannot load " + file, e);
608     }
609     catch (Throwable e) {
610       getLogger().info("Cannot load " + file, e);
611     }
612
613     return null;
614   }
615
616   @Nullable
617   public static IdeaPluginDescriptorImpl loadDescriptorFromJar(File file) {
618     return loadDescriptorFromJar(file, PLUGIN_XML);
619   }
620
621   @SuppressWarnings({"HardCodedStringLiteral"})
622   @Nullable
623   public static IdeaPluginDescriptorImpl loadDescriptor(final File file, @NonNls final String fileName) {
624     IdeaPluginDescriptorImpl descriptor = null;
625
626     if (file.isDirectory()) {
627       descriptor = loadDescriptorFromDir(file, fileName);
628
629       if (descriptor == null) {
630        File libDir = new File(file, "lib");
631        if (!libDir.isDirectory()) {
632          return null;
633        }
634        final File[] files = libDir.listFiles();
635        if (files == null || files.length == 0) {
636          return null;
637        }
638        Arrays.sort(files, new Comparator<File>() {
639          @Override
640          public int compare(File o1, File o2) {
641            if (o2.getName().startsWith(file.getName())) return Integer.MAX_VALUE;
642            if (o1.getName().startsWith(file.getName())) return -Integer.MAX_VALUE;
643            if (o2.getName().startsWith("resources")) return -Integer.MAX_VALUE;
644            if (o1.getName().startsWith("resources")) return Integer.MAX_VALUE;
645            return 0;
646          }
647        });
648        for (final File f : files) {
649          if (FileUtil.isJarOrZip(f)) {
650            descriptor = loadDescriptorFromJar(f, fileName);
651            if (descriptor != null) {
652              descriptor.setPath(file);
653              break;
654            }
655 //           getLogger().warn("Cannot load descriptor from " + f.getName() + "");
656          }
657          else if (f.isDirectory()) {
658            IdeaPluginDescriptorImpl descriptor1 = loadDescriptorFromDir(f, fileName);
659            if (descriptor1 != null) {
660              if (descriptor != null) {
661                getLogger().info("Cannot load " + file + " because two or more plugin.xml's detected");
662                return null;
663              }
664              descriptor = descriptor1;
665              descriptor.setPath(file);
666            }
667          }
668        }
669      }
670     }
671     else if (StringUtil.endsWithIgnoreCase(file.getName(), ".jar") && file.exists()) {
672       descriptor = loadDescriptorFromJar(file, fileName);
673     }
674
675     if (descriptor != null && !descriptor.getOptionalConfigs().isEmpty()) {
676       final Map<PluginId, IdeaPluginDescriptorImpl> descriptors = new HashMap<PluginId, IdeaPluginDescriptorImpl>(descriptor.getOptionalConfigs().size());
677       for (Map.Entry<PluginId, String> entry: descriptor.getOptionalConfigs().entrySet()) {
678         String optionalDescriptorName = entry.getValue();
679         assert !Comparing.equal(fileName, optionalDescriptorName) : "recursive dependency: "+ fileName;
680
681         IdeaPluginDescriptorImpl optionalDescriptor = loadDescriptor(file, optionalDescriptorName);
682         if (optionalDescriptor == null && !FileUtil.isJarOrZip(file)) {
683           for (URL url : getClassLoaderUrls()) {
684             if ("file".equals(url.getProtocol())) {
685               optionalDescriptor = loadDescriptor(new File(decodeUrl(url.getFile())), optionalDescriptorName);
686               if (optionalDescriptor != null) {
687                 break;
688               }
689             }
690           }
691         }
692         if (optionalDescriptor != null) {
693           descriptors.put(entry.getKey(), optionalDescriptor);
694         }
695         else {
696           getLogger().info("Cannot find optional descriptor " + optionalDescriptorName);
697         }
698       }
699       descriptor.setOptionalDescriptors(descriptors);
700     }
701     return descriptor;
702   }
703
704   public static void loadDescriptors(String pluginsPath,
705                                      List<IdeaPluginDescriptorImpl> result,
706                                      @Nullable StartupProgress progress,
707                                      int pluginsCount) {
708     final File pluginsHome = new File(pluginsPath);
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 HashMap<PluginId, IdeaPluginDescriptor>();
735     final StringBuffer message = new StringBuffer();
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       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 ArrayList<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 null;
830   }
831
832   static void loadDescriptorsFromClassPath(@NotNull List<IdeaPluginDescriptorImpl> result, @Nullable StartupProgress progress) {
833     Collection<URL> urls = getClassLoaderUrls();
834     String platformPrefix = System.getProperty(PlatformUtilsCore.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     try {
910       Arrays.sort(pluginDescriptors, new PluginDescriptorComparator(pluginDescriptors));
911     }
912     catch (Exception e) {
913       prepareLoadingPluginsErrorMessage(IdeBundle.message("error.plugins.were.not.loaded", e.getMessage()));
914       getLogger().info(e);
915       return findCorePlugin(pluginDescriptors);
916     }
917     return pluginDescriptors;
918   }
919
920   static void mergeOptionalConfigs(Map<PluginId, IdeaPluginDescriptorImpl> descriptors) {
921     final Map<PluginId, IdeaPluginDescriptorImpl> descriptorsWithModules = new HashMap<PluginId, IdeaPluginDescriptorImpl>(descriptors);
922     addModulesAsDependents(descriptorsWithModules);
923     for (IdeaPluginDescriptorImpl descriptor : descriptors.values()) {
924       final Map<PluginId, IdeaPluginDescriptorImpl> optionalDescriptors = descriptor.getOptionalDescriptors();
925       if (optionalDescriptors != null && !optionalDescriptors.isEmpty()) {
926         for (Map.Entry<PluginId, IdeaPluginDescriptorImpl> entry: optionalDescriptors.entrySet()) {
927           if (descriptorsWithModules.containsKey(entry.getKey())) {
928             descriptor.mergeOptionalConfig(entry.getValue());
929           }
930         }
931       }
932     }
933   }
934
935   public static void initClassLoader(@NotNull ClassLoader parentLoader, @NotNull IdeaPluginDescriptorImpl descriptor) {
936     final List<File> classPath = descriptor.getClassPath();
937     final ClassLoader loader =
938         createPluginClassLoader(classPath.toArray(new File[classPath.size()]), new ClassLoader[]{parentLoader}, descriptor);
939     descriptor.setLoader(loader);
940   }
941
942   static BuildNumber getBuildNumber() {
943     if (ourBuildNumber == null) {
944       ourBuildNumber = BuildNumber.fromString(System.getProperty("idea.plugins.compatible.build"));
945       if (ourBuildNumber == null) {
946         ourBuildNumber = BUILD_NUMBER == null ? null : BuildNumber.fromString(BUILD_NUMBER);
947         if (ourBuildNumber == null) {
948           ourBuildNumber = BuildNumber.fallback();
949         }
950       }
951     }
952     return ourBuildNumber;
953   }
954
955   static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor, IdeaPluginDescriptor[] loaded) {
956     final String idString = descriptor.getPluginId().getIdString();
957     if (idString.equals(CORE_PLUGIN_ID)) {
958       return false;
959     }
960
961     //noinspection HardCodedStringLiteral
962     final String pluginId = System.getProperty("idea.load.plugins.id");
963     if (pluginId == null) {
964       if (descriptor instanceof IdeaPluginDescriptorImpl && !descriptor.isEnabled()) return true;
965
966       if (!shouldLoadPlugins()) return true;
967     }
968     final List<String> pluginIds = pluginId == null ? null : StringUtil.split(pluginId, ",");
969
970     final boolean checkModuleDependencies = !ourAvailableModules.isEmpty() && !ourAvailableModules.contains("com.intellij.modules.all");
971     if (checkModuleDependencies && !hasModuleDependencies(descriptor)) {
972       return true;
973     }
974
975     boolean shouldLoad;
976     //noinspection HardCodedStringLiteral
977     final String loadPluginCategory = System.getProperty("idea.load.plugins.category");
978     if (loadPluginCategory != null) {
979       shouldLoad = loadPluginCategory.equals(descriptor.getCategory());
980     }
981     else {
982       if (pluginIds != null) {
983         shouldLoad = pluginIds.contains(idString);
984         if (!shouldLoad) {
985           Map<PluginId,IdeaPluginDescriptor> map = new HashMap<PluginId, IdeaPluginDescriptor>();
986           for (final IdeaPluginDescriptor pluginDescriptor : loaded) {
987             map.put(pluginDescriptor.getPluginId(), pluginDescriptor);
988           }
989           addModulesAsDependents(map);
990           for (String id : pluginIds) {
991             final IdeaPluginDescriptor descriptorFromProperty = map.get(PluginId.getId(id));
992             if (descriptorFromProperty != null && isDependent(descriptorFromProperty, descriptor.getPluginId(), map, checkModuleDependencies)) {
993               shouldLoad = true;
994               break;
995             }
996           }
997         }
998       } else {
999         shouldLoad = !getDisabledPlugins().contains(idString) &&
1000                      !getDisabledPluginVersions().get(idString).contains(descriptor.getVersion());
1001       }
1002       if (shouldLoad && descriptor instanceof IdeaPluginDescriptorImpl) {
1003         if (isIncompatible(descriptor)) return true;
1004       }
1005     }
1006
1007     return !shouldLoad;
1008   }
1009
1010   public static boolean isIncompatible(final IdeaPluginDescriptor descriptor) {
1011     try {
1012       BuildNumber buildNumber = getBuildNumber();
1013
1014       if (!StringUtil.isEmpty(descriptor.getSinceBuild())) {
1015         BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild(), descriptor.getName());
1016         if (sinceBuild.compareTo(buildNumber) > 0) {
1017           return true;
1018         }
1019       }
1020
1021       if (!StringUtil.isEmpty(descriptor.getUntilBuild()) && !buildNumber.isSnapshot()) {
1022         BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild(), descriptor.getName());
1023         if (untilBuild.compareTo(buildNumber) < 0) {
1024           return true;
1025         }
1026       }
1027     }
1028     catch (RuntimeException ignored) { }
1029
1030     return false;
1031   }
1032
1033   public static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor) {
1034     if (descriptor instanceof IdeaPluginDescriptorImpl) {
1035       IdeaPluginDescriptorImpl descriptorImpl = (IdeaPluginDescriptorImpl)descriptor;
1036       Boolean skipped = descriptorImpl.getSkipped();
1037       if (skipped != null) {
1038         return skipped.booleanValue();
1039       }
1040       boolean result = shouldSkipPlugin(descriptor, ourPlugins);
1041       descriptorImpl.setSkipped(result);
1042       return result;
1043     }
1044     return shouldSkipPlugin(descriptor, ourPlugins);
1045   }
1046
1047   static void initializePlugins(@Nullable StartupProgress progress) {
1048     configureExtensions();
1049
1050     final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress);
1051
1052     final Class callerClass = ReflectionUtil.findCallerClass(1);
1053     assert callerClass != null;
1054     final ClassLoader parentLoader = callerClass.getClassLoader();
1055
1056     final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
1057     final HashMap<String, String> disabledPluginNames = new HashMap<String, String>();
1058     for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
1059       final List<String> modules = descriptor.getModules();
1060       if (modules != null) {
1061         ourAvailableModules.addAll(modules);
1062       }
1063
1064       if (!shouldSkipPlugin(descriptor, pluginDescriptors)) {
1065         result.add(descriptor);
1066       }
1067       else {
1068         descriptor.setEnabled(false);
1069         disabledPluginNames.put(descriptor.getPluginId().getIdString(), descriptor.getName());
1070         initClassLoader(parentLoader, descriptor);
1071       }
1072     }
1073
1074     prepareLoadingPluginsErrorMessage(filterBadPlugins(result, disabledPluginNames));
1075
1076     final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new HashMap<PluginId, IdeaPluginDescriptorImpl>();
1077     for (final IdeaPluginDescriptorImpl descriptor : result) {
1078       idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
1079     }
1080
1081     final IdeaPluginDescriptor corePluginDescriptor = idToDescriptorMap.get(PluginId.getId(CORE_PLUGIN_ID));
1082     assert corePluginDescriptor != null : CORE_PLUGIN_ID + " not found; platform prefix is " + System.getProperty(PlatformUtilsCore.PLATFORM_PREFIX_KEY);
1083     for (IdeaPluginDescriptorImpl descriptor : result) {
1084       if (descriptor != corePluginDescriptor) {
1085         descriptor.insertDependency(corePluginDescriptor);
1086       }
1087     }
1088
1089     mergeOptionalConfigs(idToDescriptorMap);
1090
1091     // sort descriptors according to plugin dependencies
1092     Collections.sort(result, getPluginDescriptorComparator(idToDescriptorMap));
1093
1094     for (int i = 0; i < result.size(); i++) {
1095       ourId2Index.put(result.get(i).getPluginId(), i);
1096     }
1097
1098     int i = 0;
1099     for (final IdeaPluginDescriptorImpl pluginDescriptor : result) {
1100       if (pluginDescriptor.getPluginId().getIdString().equals(CORE_PLUGIN_ID) || pluginDescriptor.isUseCoreClassLoader()) {
1101         pluginDescriptor.setLoader(parentLoader);
1102       }
1103       else {
1104         final List<File> classPath = pluginDescriptor.getClassPath();
1105         final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
1106         final ClassLoader[] parentLoaders = getParentLoaders(idToDescriptorMap, dependentPluginIds);
1107
1108         final ClassLoader pluginClassLoader = createPluginClassLoader(classPath.toArray(new File[classPath.size()]),
1109                                                                       parentLoaders.length > 0 ? parentLoaders : new ClassLoader[] {parentLoader},
1110                                                                       pluginDescriptor);
1111         pluginDescriptor.setLoader(pluginClassLoader);
1112       }
1113
1114       if (progress != null) {
1115         progress.showProgress("", PLUGINS_PROGRESS_MAX_VALUE + (i++ / (float)result.size()) * 0.35f);
1116       }
1117     }
1118
1119     registerExtensionPointsAndExtensions(Extensions.getRootArea(), result);
1120     Extensions.getRootArea().getExtensionPoint(Extensions.AREA_LISTENER_EXTENSION_POINT).registerExtension(new AreaListener() {
1121       @Override
1122       public void areaCreated(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
1123         registerExtensionPointsAndExtensions(Extensions.getArea(areaInstance), result);
1124       }
1125
1126       @Override
1127       public void areaDisposing(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
1128       }
1129     });
1130
1131
1132     ourPlugins = pluginDescriptors;
1133   }
1134
1135   private static void registerExtensionPointsAndExtensions(ExtensionsArea area, List<IdeaPluginDescriptorImpl> loadedPlugins) {
1136     for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
1137       descriptor.registerExtensionPoints(area);
1138     }
1139
1140     Set<String> epNames = ContainerUtil.newHashSet();
1141     for (ExtensionPoint point : area.getExtensionPoints()) {
1142       epNames.add(point.getName());
1143     }
1144
1145     for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
1146       for (String epName : epNames) {
1147         descriptor.registerExtensions(area, epName);
1148       }
1149     }
1150   }
1151
1152   public static void initPlugins(@Nullable StartupProgress progress) {
1153     long start = System.currentTimeMillis();
1154     try {
1155       initializePlugins(progress);
1156     }
1157     catch (RuntimeException e) {
1158       getLogger().error(e);
1159       throw e;
1160     }
1161     getLogger().info(ourPlugins.length + " plugins initialized in " + (System.currentTimeMillis() - start) + " ms");
1162     logPlugins();
1163     ClassUtilCore.clearJarURLCache();
1164   }
1165
1166   private static class LoggerHolder {
1167     private static final Logger ourLogger = Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
1168   }
1169
1170   private static class IdeaLogProvider implements LogProvider {
1171     @Override
1172     public void error(String message) {
1173       getLogger().error(message);
1174     }
1175
1176     @Override
1177     public void error(String message, Throwable t) {
1178       getLogger().error(message, t);
1179     }
1180
1181     @Override
1182     public void error(Throwable t) {
1183       getLogger().error(t);
1184     }
1185
1186     @Override
1187     public void warn(String message) {
1188       getLogger().info(message);
1189     }
1190
1191     @Override
1192     public void warn(String message, Throwable t) {
1193       getLogger().info(message, t);
1194     }
1195
1196     @Override
1197     public void warn(Throwable t) {
1198       getLogger().info(t);
1199     }
1200   }
1201 }