2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.ide.plugins;
19 import com.intellij.ide.ClassloaderUtil;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.ide.plugins.cl.PluginClassLoader;
22 import com.intellij.idea.Main;
23 import com.intellij.openapi.application.Application;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.PathManager;
26 import com.intellij.openapi.application.impl.PluginsFacade;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.extensions.Extensions;
29 import com.intellij.openapi.extensions.LogProvider;
30 import com.intellij.openapi.extensions.PluginId;
31 import com.intellij.openapi.progress.ProcessCanceledException;
32 import com.intellij.openapi.util.BuildNumber;
33 import com.intellij.openapi.util.Comparing;
34 import com.intellij.openapi.util.Condition;
35 import com.intellij.openapi.util.io.FileUtil;
36 import com.intellij.openapi.util.text.StringUtil;
37 import com.intellij.util.ArrayUtil;
38 import com.intellij.util.Function;
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.UrlClassLoader;
44 import com.intellij.util.xmlb.XmlSerializationException;
45 import gnu.trove.THashMap;
46 import org.jetbrains.annotations.NonNls;
47 import org.jetbrains.annotations.Nullable;
48 import sun.reflect.Reflection;
52 import java.lang.reflect.InvocationTargetException;
53 import java.lang.reflect.Method;
60 @SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"}) // No logger is loaded at this time so we have to use these.
61 public class PluginManager {
63 @NonNls public static final String AREA_IDEA_PROJECT = "IDEA_PROJECT";
64 @NonNls public static final String AREA_IDEA_MODULE = "IDEA_MODULE";
65 @NonNls private static final String PROPERTY_PLUGIN_PATH = "plugin.path";
66 private static final Object PLUGIN_CLASSES_LOCK = new Object();
67 private static String myPluginError = null;
68 @NonNls public static final String CORE_PLUGIN_ID = "com.intellij";
70 @NonNls public static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
72 private static List<String> ourDisabledPlugins = null;
74 static final Object lock = new Object();
76 private static BuildNumber ourBuildNumber;
77 @NonNls public static final String PLUGIN_XML = "plugin.xml";
78 @NonNls public static final String META_INF = "META-INF";
79 private static final Map<PluginId,Integer> ourId2Index = new THashMap<PluginId, Integer>();
80 @NonNls private static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module";
81 private static final List<String> ourAvailableModules = new ArrayList<String>();
83 public static long startupStart;
85 public static class Facade extends PluginsFacade {
86 public IdeaPluginDescriptor getPlugin(PluginId id) {
87 return PluginManager.getPlugin(id);
90 public IdeaPluginDescriptor[] getPlugins() {
91 return PluginManager.getPlugins();
95 private static IdeaPluginDescriptorImpl[] ourPlugins;
96 private static Map<String, PluginId> ourPluginClasses;
99 * do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by IdeaClassLoader
101 public synchronized static IdeaPluginDescriptor[] getPlugins() {
102 if (ourPlugins == null) {
104 getLogger().info("Loaded plugins:" + StringUtil.join(ourPlugins, new Function<IdeaPluginDescriptorImpl, String>() {
105 public String fun(IdeaPluginDescriptorImpl descriptor) {
106 final String version = descriptor.getVersion();
107 return descriptor.getName() + (version != null ? " (" + version + ")" : "");
110 ClassloaderUtil.clearJarURLCache();
115 public static void invalidatePlugins() {
117 ourDisabledPlugins = null;
121 * Called via reflection
123 @SuppressWarnings({"UnusedDeclaration"})
124 protected static void start(final String mainClass, final String methodName, final String[] args) {
125 startupStart = System.nanoTime();
127 //noinspection HardCodedStringLiteral
128 ThreadGroup threadGroup = new ThreadGroup("Idea Thread Group") {
129 public void uncaughtException(Thread t, Throwable e) {
130 if (!(e instanceof ProcessCanceledException)) {
131 getLogger().error(e);
136 Runnable runnable = new Runnable() {
139 ClassloaderUtil.clearJarURLCache();
141 //noinspection AssignmentToStaticFieldFromInstanceMethod
142 PluginsFacade.INSTANCE = new PluginManager.Facade();
144 Class aClass = Class.forName(mainClass);
145 final Method method = aClass.getDeclaredMethod(methodName, (ArrayUtil.EMPTY_STRING_ARRAY).getClass());
146 method.setAccessible(true);
148 //noinspection RedundantArrayCreation
149 method.invoke(null, new Object[]{args});
151 catch (Exception e) {
153 getLogger().error("Error while accessing " + mainClass + "." + methodName + " with arguments: " + Arrays.asList(args), e);
158 //noinspection HardCodedStringLiteral
159 new Thread(threadGroup, runnable, "Idea Main Thread").start();
161 catch (Exception e) {
162 getLogger().error(e);
166 private static void initializePlugins() {
167 configureExtensions();
169 final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors();
171 final Class callerClass = Reflection.getCallerClass(1);
172 final ClassLoader parentLoader = callerClass.getClassLoader();
174 final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
175 for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
176 if (descriptor.getPluginId().getIdString().equals(CORE_PLUGIN_ID)) {
177 final List<String> modules = descriptor.getModules();
178 if (modules != null) {
179 ourAvailableModules.addAll(modules);
183 if (!shouldSkipPlugin(descriptor, pluginDescriptors)) {
184 result.add(descriptor);
187 descriptor.setEnabled(false);
188 initClassLoader(parentLoader, descriptor);
192 prepareLoadingPluginsErrorMessage(filterBadPlugins(result));
194 final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new HashMap<PluginId, IdeaPluginDescriptorImpl>();
195 for (final IdeaPluginDescriptorImpl descriptor : result) {
196 idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
199 final IdeaPluginDescriptor corePluginDescriptor = idToDescriptorMap.get(PluginId.getId(CORE_PLUGIN_ID));
200 assert corePluginDescriptor != null;
201 for (IdeaPluginDescriptorImpl descriptor : result) {
202 if (descriptor != corePluginDescriptor) {
203 descriptor.insertDependency(corePluginDescriptor);
207 mergeOptionalConfigs(idToDescriptorMap);
209 // sort descriptors according to plugin dependencies
210 Collections.sort(result, getPluginDescriptorComparator(idToDescriptorMap));
212 for (int i = 0; i < result.size(); i++) {
213 ourId2Index.put(result.get(i).getPluginId(), i);
216 for (final IdeaPluginDescriptorImpl pluginDescriptor : result) {
217 if (pluginDescriptor.getPluginId().getIdString().equals(CORE_PLUGIN_ID) || pluginDescriptor.isUseCoreClassLoader()) {
218 pluginDescriptor.setLoader(parentLoader, true);
221 final List<File> classPath = pluginDescriptor.getClassPath();
222 final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
223 final ClassLoader[] parentLoaders = getParentLoaders(idToDescriptorMap, dependentPluginIds);
225 final ClassLoader pluginClassLoader = createPluginClassLoader(classPath.toArray(new File[classPath.size()]),
226 parentLoaders.length > 0 ? parentLoaders : new ClassLoader[] {parentLoader},
228 pluginDescriptor.setLoader(pluginClassLoader, true);
231 pluginDescriptor.registerExtensions();
234 ourPlugins = pluginDescriptors;
237 public static void initClassLoader(final ClassLoader parentLoader, final IdeaPluginDescriptorImpl descriptor) {
238 final List<File> classPath = descriptor.getClassPath();
239 final ClassLoader loader =
240 createPluginClassLoader(classPath.toArray(new File[classPath.size()]), new ClassLoader[]{parentLoader}, descriptor);
241 descriptor.setLoader(loader, false);
244 public static int getPluginLoadingOrder(PluginId id) {
245 return ourId2Index.get(id);
248 private static void mergeOptionalConfigs(Map<PluginId, IdeaPluginDescriptorImpl> descriptors) {
249 final Map<PluginId, IdeaPluginDescriptorImpl> descriptorsWithModules = new HashMap<PluginId, IdeaPluginDescriptorImpl>(descriptors);
250 addModulesAsDependents(descriptorsWithModules);
251 for (IdeaPluginDescriptorImpl descriptor : descriptors.values()) {
252 final Map<PluginId, IdeaPluginDescriptorImpl> optionalDescriptors = descriptor.getOptionalDescriptors();
253 if (optionalDescriptors != null && !optionalDescriptors.isEmpty()) {
254 for (Map.Entry<PluginId, IdeaPluginDescriptorImpl> entry: optionalDescriptors.entrySet()) {
255 if (descriptorsWithModules.containsKey(entry.getKey())) {
256 descriptor.mergeOptionalConfig(entry.getValue());
263 private static void prepareLoadingPluginsErrorMessage(final String errorMessage) {
264 if (errorMessage != null) {
265 if (!Main.isHeadless() && !ApplicationManager.getApplication().isUnitTestMode()) {
266 if (myPluginError == null) {
267 myPluginError = errorMessage;
270 myPluginError += "\n" + errorMessage;
273 getLogger().error(errorMessage);
278 private static void configureExtensions() {
279 Extensions.setLogProvider(new IdeaLogProvider());
280 Extensions.registerAreaClass(AREA_IDEA_PROJECT, null);
281 Extensions.registerAreaClass(AREA_IDEA_MODULE, AREA_IDEA_PROJECT);
284 private static boolean shouldLoadPlugins() {
286 // no plugins during bootstrap
287 Class.forName("com.intellij.openapi.extensions.Extensions");
289 catch (ClassNotFoundException e) {
292 //noinspection HardCodedStringLiteral
293 final String loadPlugins = System.getProperty("idea.load.plugins");
294 return loadPlugins == null || Boolean.TRUE.toString().equals(loadPlugins);
297 public static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor) {
298 if (descriptor instanceof IdeaPluginDescriptorImpl) {
299 IdeaPluginDescriptorImpl descriptorImpl = (IdeaPluginDescriptorImpl)descriptor;
300 Boolean skipped = descriptorImpl.getSkipped();
301 if (skipped != null) {
302 return skipped.booleanValue();
304 boolean result = shouldSkipPlugin(descriptor, ourPlugins);
305 descriptorImpl.setSkipped(result);
308 return shouldSkipPlugin(descriptor, ourPlugins);
311 private static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor, IdeaPluginDescriptor[] loaded) {
312 final String idString = descriptor.getPluginId().getIdString();
313 if (idString.equals(CORE_PLUGIN_ID)) {
317 //noinspection HardCodedStringLiteral
318 final String pluginId = System.getProperty("idea.load.plugins.id");
319 if (pluginId == null) {
320 if (descriptor instanceof IdeaPluginDescriptorImpl && !((IdeaPluginDescriptorImpl)descriptor).isEnabled()) return true;
322 if (!shouldLoadPlugins()) return true;
325 final boolean checkModuleDependencies = !ourAvailableModules.isEmpty() && !ourAvailableModules.contains("com.intellij.modules.all");
326 if (checkModuleDependencies && !hasModuleDependencies(descriptor)) {
331 //noinspection HardCodedStringLiteral
332 final String loadPluginCategory = System.getProperty("idea.load.plugins.category");
333 if (loadPluginCategory != null) {
334 shouldLoad = loadPluginCategory.equals(descriptor.getCategory());
337 if (pluginId != null) {
338 shouldLoad = pluginId.equals(idString);
340 Map<PluginId,IdeaPluginDescriptor> map = new HashMap<PluginId, IdeaPluginDescriptor>();
341 for (final IdeaPluginDescriptor pluginDescriptor : loaded) {
342 map.put(pluginDescriptor.getPluginId(), pluginDescriptor);
344 addModulesAsDependents(map);
345 final IdeaPluginDescriptor descriptorFromProperty = map.get(PluginId.getId(pluginId));
346 shouldLoad = descriptorFromProperty != null && isDependent(descriptorFromProperty, descriptor.getPluginId(), map,
347 checkModuleDependencies);
350 shouldLoad = !getDisabledPlugins().contains(idString);
352 if (shouldLoad && descriptor instanceof IdeaPluginDescriptorImpl) {
353 if (isIncompatible(descriptor)) return true;
360 private static <T extends IdeaPluginDescriptor> void addModulesAsDependents(final Map<PluginId, T> map) {
361 for (String module : ourAvailableModules) {
362 // fake plugin descriptors to satisfy dependencies
363 map.put(PluginId.getId(module), (T) new IdeaPluginDescriptorImpl(null));
367 private static boolean hasModuleDependencies(final IdeaPluginDescriptor descriptor) {
368 final PluginId[] dependentPluginIds = descriptor.getDependentPluginIds();
369 for (PluginId dependentPluginId : dependentPluginIds) {
370 if (isModuleDependency(dependentPluginId)) {
377 public static boolean isModuleDependency(final PluginId dependentPluginId) {
378 return dependentPluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX);
381 private static boolean isDependent(final IdeaPluginDescriptor descriptor,
383 Map<PluginId, IdeaPluginDescriptor> map,
384 final boolean checkModuleDependencies) {
385 for (PluginId id: descriptor.getDependentPluginIds()) {
386 if (!checkModuleDependencies && isModuleDependency(id)) {
392 final IdeaPluginDescriptor depDescriptor = map.get(id);
393 if (depDescriptor != null && isDependent(depDescriptor, on, map, checkModuleDependencies)) {
400 public static boolean isIncompatible(final IdeaPluginDescriptor descriptor) {
401 BuildNumber buildNumber = getBuildNumber();
403 if (!StringUtil.isEmpty(descriptor.getSinceBuild())) {
404 BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild());
405 if (sinceBuild.compareTo(buildNumber) > 0) {
410 if (!StringUtil.isEmpty(descriptor.getUntilBuild()) && !buildNumber.isSnapshot()) {
411 BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild());
412 if (untilBuild.compareTo(buildNumber) < 0) return true;
418 private static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap) {
419 final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
420 final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
422 if (!builder.isAcyclic()) {
423 final Pair<String,String> circularDependency = builder.getCircularDependency();
424 throw new Exception("Cyclic dependencies between plugins are not allowed: \"" + circularDependency.getFirst() + "\" and \"" + circularDependency.getSecond() + "");
427 final Comparator<PluginId> idComparator = builder.comparator();
428 return new Comparator<IdeaPluginDescriptor>() {
429 public int compare(IdeaPluginDescriptor o1, IdeaPluginDescriptor o2) {
430 return idComparator.compare(o1.getPluginId(), o2.getPluginId());
435 private static Graph<PluginId> createPluginIdGraph(final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap) {
436 final PluginId[] ids = idToDescriptorMap.keySet().toArray(new PluginId[idToDescriptorMap.size()]);
437 return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<PluginId>() {
438 public Collection<PluginId> getNodes() {
439 return Arrays.asList(ids);
442 public Iterator<PluginId> getIn(PluginId pluginId) {
443 final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
444 ArrayList<PluginId> plugins = new ArrayList<PluginId>();
445 for(PluginId dependentPluginId: descriptor.getDependentPluginIds()) {
446 // check for missing optional dependency
447 if (idToDescriptorMap.containsKey(dependentPluginId)) {
448 plugins.add(dependentPluginId);
451 return plugins.iterator();
456 private static ClassLoader[] getParentLoaders(Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap, PluginId[] pluginIds) {
457 if (isUnitTestMode()) return new ClassLoader[0];
458 final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
459 for (final PluginId id : pluginIds) {
460 IdeaPluginDescriptor pluginDescriptor = idToDescriptorMap.get(id);
461 if (pluginDescriptor == null) {
462 continue; // Might be an optional dependency
465 final ClassLoader loader = pluginDescriptor.getPluginClassLoader();
466 if (loader == null) {
467 getLogger().assertTrue(false, "Plugin class loader should be initialized for plugin " + id);
469 classLoaders.add(loader);
471 return classLoaders.toArray(new ClassLoader[classLoaders.size()]);
474 public static IdeaPluginDescriptorImpl[] loadDescriptors() {
475 if (ClassloaderUtil.isLoadingOfExternalPluginsDisabled()) {
476 return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
479 final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
481 loadDescriptors(PathManager.getPluginsPath(), result);
482 Application application = ApplicationManager.getApplication();
483 if (application == null || !application.isUnitTestMode()) {
484 loadDescriptors(PathManager.getPreinstalledPluginsPath(), result);
487 loadDescriptorsFromProperty(result);
489 loadDescriptorsFromClassPath(result);
491 IdeaPluginDescriptorImpl[] pluginDescriptors = result.toArray(new IdeaPluginDescriptorImpl[result.size()]);
493 Arrays.sort(pluginDescriptors, new PluginDescriptorComparator(pluginDescriptors));
495 catch (Exception e) {
496 prepareLoadingPluginsErrorMessage(IdeBundle.message("error.plugins.were.not.loaded", e.getMessage()));
498 pluginDescriptors = IdeaPluginDescriptorImpl.EMPTY_ARRAY;
500 return pluginDescriptors;
503 public static void reportPluginError() {
504 if (myPluginError != null) {
505 JOptionPane.showMessageDialog(null, myPluginError, IdeBundle.message("title.plugin.error"), JOptionPane.ERROR_MESSAGE);
506 myPluginError = null;
510 private static void loadDescriptorsFromProperty(final List<IdeaPluginDescriptorImpl> result) {
511 final String pathProperty = System.getProperty(PROPERTY_PLUGIN_PATH);
512 if (pathProperty == null) return;
514 for (java.util.StringTokenizer t = new java.util.StringTokenizer(pathProperty, File.pathSeparator); t.hasMoreTokens();) {
515 String s = t.nextToken();
516 final IdeaPluginDescriptorImpl ideaPluginDescriptor = loadDescriptor(new File(s), PLUGIN_XML);
517 if (ideaPluginDescriptor != null) {
518 result.add(ideaPluginDescriptor);
523 @SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
524 private static void loadDescriptorsFromClassPath(final List<IdeaPluginDescriptorImpl> result) {
526 final Collection<URL> urls = getClassLoaderUrls();
527 for (URL url : urls) {
528 final String protocol = url.getProtocol();
529 if ("file".equals(protocol)) {
530 final File file = new File(URLDecoder.decode(url.getFile()));
531 //final String canonicalPath = file.getCanonicalPath();
532 //if (!canonicalPath.startsWith(homePath) || canonicalPath.endsWith(".jar")) continue;
533 //if (!canonicalPath.startsWith(homePath)) continue;
535 final String platformPrefix = System.getProperty("idea.platform.prefix");
536 IdeaPluginDescriptorImpl platformPluginDescriptor = null;
537 if (platformPrefix != null) {
538 platformPluginDescriptor = loadDescriptor(file, platformPrefix + "Plugin.xml");
539 if (platformPluginDescriptor != null && !result.contains(platformPluginDescriptor)) {
540 platformPluginDescriptor.setUseCoreClassLoader(true);
541 result.add(platformPluginDescriptor);
545 IdeaPluginDescriptorImpl pluginDescriptor = loadDescriptor(file, PLUGIN_XML);
546 if (platformPrefix != null && pluginDescriptor != null && pluginDescriptor.getName().equals("IDEA CORE")) {
549 if (pluginDescriptor != null && !result.contains(pluginDescriptor)) {
550 if (platformPluginDescriptor != null) {
551 // if we found a regular plugin.xml in the same .jar/root as a platform-prefixed descriptor, use the core loader for it too
552 pluginDescriptor.setUseCoreClassLoader(true);
554 result.add(pluginDescriptor);
559 catch (Exception e) {
560 System.err.println("Error loading plugins from classpath:");
565 @SuppressWarnings({"EmptyCatchBlock"})
566 private static Collection<URL> getClassLoaderUrls() {
567 final ClassLoader classLoader = PluginManager.class.getClassLoader();
568 final Class<? extends ClassLoader> aClass = classLoader.getClass();
569 if (aClass.getName().equals(UrlClassLoader.class.getName())) {
571 return (List<URL>)aClass.getDeclaredMethod("getUrls").invoke(classLoader);
573 catch (IllegalAccessException e) {
575 catch (InvocationTargetException e) {
577 catch (NoSuchMethodException e) {
580 if (classLoader instanceof URLClassLoader) {
581 return Arrays.asList(((URLClassLoader)classLoader).getURLs());
584 return Collections.emptyList();
588 private static String filterBadPlugins(List<IdeaPluginDescriptorImpl> result) {
589 final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new HashMap<PluginId, IdeaPluginDescriptorImpl>();
590 final StringBuffer message = new StringBuffer();
591 boolean pluginsWithoutIdFound = false;
592 for (Iterator<IdeaPluginDescriptorImpl> it = result.iterator(); it.hasNext();) {
593 final IdeaPluginDescriptorImpl descriptor = it.next();
594 final PluginId id = descriptor.getPluginId();
596 pluginsWithoutIdFound = true;
598 if (idToDescriptorMap.containsKey(id)) {
599 if (message.length() > 0) {
600 message.append("\n");
602 message.append(IdeBundle.message("message.duplicate.plugin.id"));
606 else if (descriptor.isEnabled()) {
607 idToDescriptorMap.put(id, descriptor);
610 addModulesAsDependents(idToDescriptorMap);
611 final List<String> disabledPluginIds = new ArrayList<String>();
612 for (final Iterator<IdeaPluginDescriptorImpl> it = result.iterator(); it.hasNext();) {
613 final IdeaPluginDescriptorImpl pluginDescriptor = it.next();
614 checkDependants(pluginDescriptor, new Function<PluginId, IdeaPluginDescriptor>() {
615 public IdeaPluginDescriptor fun(final PluginId pluginId) {
616 return idToDescriptorMap.get(pluginId);
618 }, new Condition<PluginId>() {
619 public boolean value(final PluginId pluginId) {
620 if (!idToDescriptorMap.containsKey(pluginId)) {
621 if (message.length() > 0) {
622 message.append("\n");
624 pluginDescriptor.setEnabled(false);
625 disabledPluginIds.add(pluginDescriptor.getPluginId().getIdString());
626 message.append(getDisabledPlugins().contains(pluginId.getIdString())
627 ? IdeBundle.message("error.required.plugin.disabled", pluginDescriptor.getPluginId(), pluginId)
628 : IdeBundle.message("error.required.plugin.not.installed", pluginDescriptor.getPluginId(), pluginId));
636 if (!disabledPluginIds.isEmpty()) {
638 saveDisabledPlugins(disabledPluginIds, true);
640 catch (IOException e) {
641 getLogger().error(e);
644 if (pluginsWithoutIdFound) {
645 if (message.length() > 0) {
646 message.append("\n");
648 message.append(IdeBundle.message("error.plugins.without.id.found"));
650 if (message.length() > 0) {
651 message.insert(0, IdeBundle.message("error.problems.found.loading.plugins"));
652 return message.toString();
657 public static void checkDependants(final IdeaPluginDescriptor pluginDescriptor,
658 final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
659 final Condition<PluginId> check) {
660 checkDependants(pluginDescriptor, pluginId2Descriptor, check, new HashSet<PluginId>());
663 private static boolean checkDependants(final IdeaPluginDescriptor pluginDescriptor,
664 final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
665 final Condition<PluginId> check,
666 final Set<PluginId> processed) {
667 processed.add(pluginDescriptor.getPluginId());
668 final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
669 final Set<PluginId> optionalDependencies = new HashSet<PluginId>(Arrays.asList(pluginDescriptor.getOptionalDependentPluginIds()));
670 for (final PluginId dependentPluginId : dependentPluginIds) {
671 if (processed.contains(dependentPluginId)) continue;
673 // TODO[yole] should this condition be a parameter?
674 if (isModuleDependency(dependentPluginId) && (ourAvailableModules.isEmpty() || ourAvailableModules.contains(dependentPluginId.getIdString()))) {
677 if (!optionalDependencies.contains(dependentPluginId)) {
678 if (!check.value(dependentPluginId)) {
681 final IdeaPluginDescriptor dependantPluginDescriptor = pluginId2Descriptor.fun(dependentPluginId);
682 if (dependantPluginDescriptor != null && !checkDependants(dependantPluginDescriptor, pluginId2Descriptor, check, processed)) {
690 static BuildNumber getBuildNumber() {
691 if (ourBuildNumber == null) {
692 ourBuildNumber = BuildNumber.fromString(System.getProperty("idea.plugins.compatible.build"));
693 if (ourBuildNumber == null) {
696 FileUtil.findFirstThatExist(PathManager.getHomePath() + "/build.txt", PathManager.getHomePath() + "/community/build.txt");
698 if (buildTxtFile != null) {
699 ourBuildNumber = BuildNumber.fromString(new String(FileUtil.loadFileText(buildTxtFile)).trim());
702 ourBuildNumber = BuildNumber.fromString("94.SNAPSHOT");
705 catch (IOException e) {
706 ourBuildNumber = BuildNumber.fromString("94.SNAPSHOT");
710 return ourBuildNumber;
714 private static void loadDescriptors(String pluginsPath, List<IdeaPluginDescriptorImpl> result) {
715 final File pluginsHome = new File(pluginsPath);
716 final File[] files = pluginsHome.listFiles();
718 for (File file : files) {
719 final IdeaPluginDescriptorImpl descriptor = loadDescriptor(file, PLUGIN_XML);
720 if (descriptor == null) continue;
721 int oldIndex = result.indexOf(descriptor);
723 final IdeaPluginDescriptorImpl oldDescriptor = result.get(oldIndex);
724 if (StringUtil.compareVersionNumbers(oldDescriptor.getVersion(), descriptor.getVersion()) < 0) {
725 result.set(oldIndex, descriptor);
729 result.add(descriptor);
735 @SuppressWarnings({"HardCodedStringLiteral"})
737 public static IdeaPluginDescriptorImpl loadDescriptor(final File file, final @NonNls String fileName) {
738 IdeaPluginDescriptorImpl descriptor = null;
740 if (file.isDirectory()) {
741 descriptor = loadDescriptorFromDir(file, fileName);
743 if (descriptor == null) {
744 File libDir = new File(file, "lib");
745 if (!libDir.isDirectory()) {
748 final File[] files = libDir.listFiles();
749 if (files == null || files.length == 0) {
752 for (final File f : files) {
753 if (ClassloaderUtil.isJarOrZip(f)) {
754 IdeaPluginDescriptorImpl descriptor1 = loadDescriptorFromJar(f, fileName);
755 if (descriptor1 != null) {
756 if (descriptor != null) {
757 getLogger().info("Cannot load " + file + " because two or more plugin.xml's detected");
760 descriptor = descriptor1;
761 descriptor.setPath(file);
764 else if (f.isDirectory()) {
765 IdeaPluginDescriptorImpl descriptor1 = loadDescriptorFromDir(f, fileName);
766 if (descriptor1 != null) {
767 if (descriptor != null) {
768 getLogger().info("Cannot load " + file + " because two or more plugin.xml's detected");
771 descriptor = descriptor1;
772 descriptor.setPath(file);
778 else if (StringUtil.endsWithIgnoreCase(file.getName(), ".jar")) {
779 descriptor = loadDescriptorFromJar(file, fileName);
782 if (descriptor != null && !descriptor.getOptionalConfigs().isEmpty()) {
783 final Map<PluginId, IdeaPluginDescriptorImpl> descriptors = new HashMap<PluginId, IdeaPluginDescriptorImpl>(descriptor.getOptionalConfigs().size());
784 for (Map.Entry<PluginId, String> entry: descriptor.getOptionalConfigs().entrySet()) {
785 final IdeaPluginDescriptorImpl optionalDescriptor = loadDescriptor(file, entry.getValue());
786 if (optionalDescriptor != null) {
787 descriptors.put(entry.getKey(), optionalDescriptor);
790 descriptor.setOptionalDescriptors(descriptors);
796 private static IdeaPluginDescriptorImpl loadDescriptorFromDir(final File file, @NonNls String fileName) {
797 IdeaPluginDescriptorImpl descriptor = null;
798 File descriptorFile = new File(file, META_INF + File.separator + fileName);
799 if (descriptorFile.exists()) {
800 descriptor = new IdeaPluginDescriptorImpl(file);
803 descriptor.readExternal(descriptorFile.toURL());
805 catch (Exception e) {
806 System.err.println("Cannot load: " + descriptorFile.getAbsolutePath());
814 public static IdeaPluginDescriptorImpl loadDescriptorFromJar(File file) {
815 return loadDescriptorFromJar(file, PLUGIN_XML);
819 private static IdeaPluginDescriptorImpl loadDescriptorFromJar(File file, @NonNls String fileName) {
822 IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
824 URI fileURL = file.toURI();
825 URL jarURL = new URL(
826 "jar:" + StringUtil.replace(fileURL.toASCIIString(), "!", "%21") + "!/META-INF/" + fileName
829 descriptor.readExternal(jarURL);
832 catch (XmlSerializationException e) {
833 getLogger().info("Cannot load " + file, e);
834 prepareLoadingPluginsErrorMessage("Plugin file " + file.getName() + " contains invalid plugin descriptor file.");
836 catch (FileNotFoundException e) {
839 catch (Exception e) {
840 getLogger().info("Cannot load " + file, e);
847 private static ClassLoader createPluginClassLoader(final File[] classPath,
848 final ClassLoader[] parentLoaders,
849 IdeaPluginDescriptor pluginDescriptor) {
851 if (pluginDescriptor.getUseIdeaClassLoader()) {
853 final ClassLoader loader = PluginManager.class.getClassLoader();
854 final Method addUrlMethod = getAddUrlMethod(loader);
857 for (File aClassPath : classPath) {
858 final File file = aClassPath.getCanonicalFile();
859 addUrlMethod.invoke(loader, file.toURL());
864 catch (NoSuchMethodException e) {
865 e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
867 catch (IOException e) {
868 e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
870 catch (IllegalAccessException e) {
871 e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
873 catch (InvocationTargetException e) {
874 e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
878 PluginId pluginId = pluginDescriptor.getPluginId();
879 File pluginRoot = pluginDescriptor.getPath();
881 if (isUnitTestMode()) return null;
883 final List<URL> urls = new ArrayList<URL>(classPath.length);
884 for (File aClassPath : classPath) {
885 final File file = aClassPath.getCanonicalFile(); // it is critical not to have "." and ".." in classpath elements
886 urls.add(file.toURL());
888 return new PluginClassLoader(urls, parentLoaders, pluginId, pluginRoot);
890 catch (MalformedURLException e) {
893 catch (IOException e) {
899 @SuppressWarnings({"HardCodedStringLiteral"})
900 private static Method getAddUrlMethod(final ClassLoader loader) throws NoSuchMethodException {
901 if (loader instanceof URLClassLoader) {
902 final Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
903 addUrlMethod.setAccessible(true);
907 return loader.getClass().getDeclaredMethod("addURL", URL.class);
911 public static boolean isPluginInstalled(PluginId id) {
912 return (getPlugin(id) != null);
916 public static IdeaPluginDescriptor getPlugin(PluginId id) {
917 final IdeaPluginDescriptor[] plugins = getPlugins();
918 for (final IdeaPluginDescriptor plugin : plugins) {
919 if (Comparing.equal(id, plugin.getPluginId())) {
926 public static void addPluginClass(String className, PluginId pluginId) {
927 synchronized(PLUGIN_CLASSES_LOCK) {
928 if (ourPluginClasses == null) {
929 ourPluginClasses = new THashMap<String, PluginId>();
931 ourPluginClasses.put(className, pluginId);
935 public static boolean isPluginClass(String className) {
936 return getPluginByClassName(className) != null;
940 public static PluginId getPluginByClassName(String className) {
941 synchronized (PLUGIN_CLASSES_LOCK) {
942 return ourPluginClasses != null ? ourPluginClasses.get(className) : null;
946 public static boolean disablePlugin(String id) {
947 if (getDisabledPlugins().contains(id)) return false;
948 getDisabledPlugins().add(id);
950 saveDisabledPlugins(getDisabledPlugins(), false);
952 catch (IOException e) {
958 public static void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException {
959 File plugins = new File(PathManager.getConfigPath(), PluginManager.DISABLED_PLUGINS_FILENAME);
960 if (!plugins.isFile()) {
961 FileUtil.ensureCanCreateFile(plugins);
963 PrintWriter printWriter = null;
965 printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins, append)));
966 for (String id : ids) {
967 printWriter.println(id);
972 if (printWriter != null) {
978 public static List<String> getDisabledPlugins() {
979 if (ourDisabledPlugins == null) {
980 ourDisabledPlugins = new ArrayList<String>();
981 if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
982 loadDisabledPlugins(PathManager.getConfigPath(), ourDisabledPlugins);
985 return ourDisabledPlugins;
988 public static void loadDisabledPlugins(final String configPath, final Collection<String> disabledPlugins) {
989 final File file = new File(configPath, DISABLED_PLUGINS_FILENAME);
991 BufferedReader reader = null;
993 reader = new BufferedReader(new FileReader(file));
995 while ((id = reader.readLine()) != null) {
996 disabledPlugins.add(id.trim());
999 catch (IOException e) {
1004 if (reader != null) {
1008 catch (IOException e) {
1015 public static void disableIncompatiblePlugin(final Object cause, final Throwable ex) {
1016 final PluginId pluginId = getPluginByClassName(cause.getClass().getName());
1017 if (pluginId != null && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
1018 final boolean success = disablePlugin(pluginId.getIdString());
1019 SwingUtilities.invokeLater(new Runnable() {
1021 JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
1022 "Incompatible plugin detected: " + pluginId.getIdString() +
1023 (success ? "\nThe plugin has been disabled" : ""),
1025 JOptionPane.ERROR_MESSAGE);
1030 // should never happen
1031 throw new RuntimeException(ex);
1035 private static boolean isUnitTestMode() {
1036 final Application app = ApplicationManager.getApplication();
1037 return app != null && app.isUnitTestMode();
1040 private static class IdeaLogProvider implements LogProvider {
1041 public void error(String message) {
1042 getLogger().error(message);
1045 public void error(String message, Throwable t) {
1046 getLogger().error(message, t);
1049 public void error(Throwable t) {
1050 getLogger().error(t);
1053 public void warn(String message) {
1054 getLogger().info(message);
1057 public void warn(String message, Throwable t) {
1058 getLogger().info(message, t);
1061 public void warn(Throwable t) {
1062 getLogger().info(t);
1066 private static class LoggerHolder {
1067 private static final Logger ourLogger = Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
1070 public static Logger getLogger() {
1071 return LoggerHolder.ourLogger;
1074 private static class ClassCounter {
1075 private String myPluginId;
1076 private int myCount;
1078 private ClassCounter(String pluginId) {
1079 myPluginId = pluginId;
1083 private void increment() {
1088 public String toString() {
1089 return myPluginId + ": " + myCount;
1093 public static void dumpPluginClassStatistics() {
1094 if (!Boolean.valueOf(System.getProperty("idea.is.internal")).booleanValue()) return;
1095 Map<String, ClassCounter> pluginToClassMap = new HashMap<String, ClassCounter>();
1096 synchronized (PLUGIN_CLASSES_LOCK) {
1097 for (Map.Entry<String, PluginId> entry : ourPluginClasses.entrySet()) {
1098 String id = entry.getValue().toString();
1099 final ClassCounter counter = pluginToClassMap.get(id);
1100 if (counter != null) {
1101 counter.increment();
1104 pluginToClassMap.put(id, new ClassCounter(id));
1108 List<ClassCounter> counters = new ArrayList<ClassCounter>(pluginToClassMap.values());
1109 Collections.sort(counters, new Comparator<ClassCounter>() {
1110 public int compare(ClassCounter o1, ClassCounter o2) {
1111 return o2.myCount - o1.myCount;
1114 for (ClassCounter counter : counters) {
1115 getLogger().info(counter.toString());