blob: d80d36822eb1f0aa201132261a5e365aaae2d77c [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ide.plugins;
import com.intellij.ide.ClassUtilCore;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.StartupProgress;
import com.intellij.ide.plugins.cl.PluginClassLoader;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.ExtensionAreas;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.*;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.util.io.ZipFileCache;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.execution.ParametersListUtil;
import com.intellij.util.graph.CachingSemiGraph;
import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.Graph;
import com.intellij.util.graph.GraphGenerator;
import com.intellij.util.xmlb.XmlSerializationException;
import gnu.trove.THashMap;
import gnu.trove.TIntProcedure;
import org.jdom.Document;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class PluginManagerCore {
@NonNls public static final String DISABLED_PLUGINS_FILENAME = "disabled_plugins.txt";
@NonNls public static final String CORE_PLUGIN_ID = "com.intellij";
@NonNls public static final String META_INF = "META-INF";
@NonNls public static final String PLUGIN_XML = "plugin.xml";
public static final float PLUGINS_PROGRESS_MAX_VALUE = 0.3f;
static final Map<PluginId,Integer> ourId2Index = new THashMap<PluginId, Integer>();
@NonNls static final String MODULE_DEPENDENCY_PREFIX = "com.intellij.module";
static final Map<String, IdeaPluginDescriptorImpl> ourModulesToContainingPlugins = new HashMap<String, IdeaPluginDescriptorImpl>();
static final PluginClassCache ourPluginClasses = new PluginClassCache();
@NonNls static final String SPECIAL_IDEA_PLUGIN = "IDEA CORE";
static final String DISABLE = "disable";
static final String ENABLE = "enable";
static final String EDIT = "edit";
@NonNls private static final String PROPERTY_PLUGIN_PATH = "plugin.path";
static List<String> ourDisabledPlugins = null;
static MultiMap<String, String> ourBrokenPluginVersions = null;
static IdeaPluginDescriptor[] ourPlugins;
static String myPluginError = null;
static List<String> myPlugins2Disable = null;
static LinkedHashSet<String> myPlugins2Enable = null;
public static String BUILD_NUMBER;
private static BuildNumber ourBuildNumber;
/**
* do not call this method during bootstrap, should be called in a copy of PluginManager, loaded by IdeaClassLoader
*/
public static synchronized IdeaPluginDescriptor[] getPlugins() {
if (ourPlugins == null) {
initPlugins(null);
}
return ourPlugins;
}
public static void loadDisabledPlugins(final String configPath, final Collection<String> disabledPlugins) {
final File file = new File(configPath, DISABLED_PLUGINS_FILENAME);
if (file.isFile()) {
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
try {
String id;
while ((id = reader.readLine()) != null) {
disabledPlugins.add(id.trim());
}
}
finally {
reader.close();
}
}
catch (IOException ignored) { }
}
}
@NotNull
public static List<String> getDisabledPlugins() {
if (ourDisabledPlugins == null) {
ourDisabledPlugins = new ArrayList<String>();
if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
loadDisabledPlugins(PathManager.getConfigPath(), ourDisabledPlugins);
}
}
return ourDisabledPlugins;
}
public static boolean isBrokenPlugin(IdeaPluginDescriptor descriptor) {
return getBrokenPluginVersions().get(descriptor.getPluginId().getIdString()).contains(descriptor.getVersion());
}
public static MultiMap<String, String> getBrokenPluginVersions() {
if (ourBrokenPluginVersions == null) {
ourBrokenPluginVersions = MultiMap.createSet();
if (System.getProperty("idea.ignore.disabled.plugins") == null && !isUnitTestMode()) {
BufferedReader br = new BufferedReader(new InputStreamReader(PluginManagerCore.class.getResourceAsStream("/brokenPlugins.txt")));
try {
String s;
while ((s = br.readLine()) != null) {
s = s.trim();
if (s.startsWith("//")) continue;
List<String> tokens = ParametersListUtil.parse(s);
if (tokens.isEmpty()) continue;
if (tokens.size() == 1) {
throw new RuntimeException("brokenPlugins.txt is broken. The line contains plugin name, but does not contains version: " + s);
}
String pluginId = tokens.get(0);
List<String> versions = tokens.subList(1, tokens.size());
ourBrokenPluginVersions.putValues(pluginId, versions);
}
}
catch (IOException e) {
throw new RuntimeException("Failed to read /brokenPlugins.txt", e);
}
finally {
StreamUtil.closeStream(br);
}
}
}
return ourBrokenPluginVersions;
}
static boolean isUnitTestMode() {
final Application app = ApplicationManager.getApplication();
return app != null && app.isUnitTestMode();
}
public static void savePluginsList(Collection<String> ids, boolean append, File plugins) throws IOException {
if (!plugins.isFile()) {
FileUtil.ensureCanCreateFile(plugins);
}
PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(plugins, append)));
try {
for (String id : ids) {
printWriter.println(id);
}
printWriter.flush();
}
finally {
printWriter.close();
}
}
public static boolean disablePlugin(String id) {
if (getDisabledPlugins().contains(id)) return false;
getDisabledPlugins().add(id);
try {
saveDisabledPlugins(getDisabledPlugins(), false);
}
catch (IOException e) {
return false;
}
return true;
}
public static boolean enablePlugin(String id) {
if (!getDisabledPlugins().contains(id)) return false;
getDisabledPlugins().remove(id);
try {
saveDisabledPlugins(getDisabledPlugins(), false);
}
catch (IOException e) {
return false;
}
return true;
}
public static void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException {
File plugins = new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME);
savePluginsList(ids, append, plugins);
ourDisabledPlugins = null;
}
public static Logger getLogger() {
return LoggerHolder.ourLogger;
}
public static int getPluginLoadingOrder(PluginId id) {
return ourId2Index.get(id);
}
public static boolean isModuleDependency(final PluginId dependentPluginId) {
return dependentPluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX);
}
public static void checkDependants(final IdeaPluginDescriptor pluginDescriptor,
final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
final Condition<PluginId> check) {
checkDependants(pluginDescriptor, pluginId2Descriptor, check, new HashSet<PluginId>());
}
private static boolean checkDependants(final IdeaPluginDescriptor pluginDescriptor,
final Function<PluginId, IdeaPluginDescriptor> pluginId2Descriptor,
final Condition<PluginId> check,
final Set<PluginId> processed) {
processed.add(pluginDescriptor.getPluginId());
final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
final Set<PluginId> optionalDependencies = new HashSet<PluginId>(Arrays.asList(pluginDescriptor.getOptionalDependentPluginIds()));
for (final PluginId dependentPluginId : dependentPluginIds) {
if (processed.contains(dependentPluginId)) continue;
// TODO[yole] should this condition be a parameter?
if (isModuleDependency(dependentPluginId) && (ourModulesToContainingPlugins.isEmpty() || ourModulesToContainingPlugins.containsKey(
dependentPluginId.getIdString()))) {
continue;
}
if (!optionalDependencies.contains(dependentPluginId)) {
if (!check.value(dependentPluginId)) {
return false;
}
final IdeaPluginDescriptor dependantPluginDescriptor = pluginId2Descriptor.fun(dependentPluginId);
if (dependantPluginDescriptor != null && !checkDependants(dependantPluginDescriptor, pluginId2Descriptor, check, processed)) {
return false;
}
}
}
return true;
}
public static void addPluginClass(@NotNull String className, PluginId pluginId, boolean loaded) {
ourPluginClasses.addPluginClass(className, pluginId, loaded);
}
@Nullable
public static PluginId getPluginByClassName(@NotNull String className) {
return ourPluginClasses.getPluginByClassName(className);
}
public static void dumpPluginClassStatistics() {
ourPluginClasses.dumpPluginClassStatistics();
}
static boolean isDependent(final IdeaPluginDescriptor descriptor,
final PluginId on,
Map<PluginId, IdeaPluginDescriptor> map,
final boolean checkModuleDependencies) {
for (PluginId id: descriptor.getDependentPluginIds()) {
if (ArrayUtil.contains(id, (Object[])descriptor.getOptionalDependentPluginIds())) {
continue;
}
if (!checkModuleDependencies && isModuleDependency(id)) {
continue;
}
if (id.equals(on)) {
return true;
}
final IdeaPluginDescriptor depDescriptor = map.get(id);
if (depDescriptor != null && isDependent(depDescriptor, on, map, checkModuleDependencies)) {
return true;
}
}
return false;
}
static boolean hasModuleDependencies(final IdeaPluginDescriptor descriptor) {
final PluginId[] dependentPluginIds = descriptor.getDependentPluginIds();
for (PluginId dependentPluginId : dependentPluginIds) {
if (isModuleDependency(dependentPluginId)) {
return true;
}
}
return false;
}
static boolean shouldLoadPlugins() {
try {
// no plugins during bootstrap
Class.forName("com.intellij.openapi.extensions.Extensions");
}
catch (ClassNotFoundException e) {
return false;
}
//noinspection HardCodedStringLiteral
final String loadPlugins = System.getProperty("idea.load.plugins");
return loadPlugins == null || Boolean.TRUE.toString().equals(loadPlugins);
}
static void configureExtensions() {
Extensions.setLogProvider(new IdeaLogProvider());
Extensions.registerAreaClass(ExtensionAreas.IDEA_PROJECT, null);
Extensions.registerAreaClass(ExtensionAreas.IDEA_MODULE, ExtensionAreas.IDEA_PROJECT);
}
private static Method getAddUrlMethod(final ClassLoader loader) {
return ReflectionUtil.getDeclaredMethod(loader instanceof URLClassLoader ? URLClassLoader.class : loader.getClass(), "addURL", URL.class);
}
@Nullable
static ClassLoader createPluginClassLoader(@NotNull File[] classPath,
@NotNull ClassLoader[] parentLoaders,
@NotNull IdeaPluginDescriptor pluginDescriptor) {
if (pluginDescriptor.getUseIdeaClassLoader()) {
try {
final ClassLoader loader = PluginManagerCore.class.getClassLoader();
final Method addUrlMethod = getAddUrlMethod(loader);
for (File aClassPath : classPath) {
final File file = aClassPath.getCanonicalFile();
addUrlMethod.invoke(loader, file.toURI().toURL());
}
return loader;
}
catch (IOException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
}
PluginId pluginId = pluginDescriptor.getPluginId();
File pluginRoot = pluginDescriptor.getPath();
//if (classPath.length == 0) return null;
if (isUnitTestMode()) return null;
try {
final List<URL> urls = new ArrayList<URL>(classPath.length);
for (File aClassPath : classPath) {
final File file = aClassPath.getCanonicalFile(); // it is critical not to have "." and ".." in classpath elements
urls.add(file.toURI().toURL());
}
return new PluginClassLoader(urls, parentLoaders, pluginId, pluginDescriptor.getVersion(), pluginRoot);
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void invalidatePlugins() {
ourPlugins = null;
ourDisabledPlugins = null;
}
public static boolean isPluginClass(String className) {
return ourPlugins != null && getPluginByClassName(className) != null;
}
static void logPlugins() {
List<String> loadedBundled = new ArrayList<String>();
List<String> disabled = new ArrayList<String>();
List<String> loadedCustom = new ArrayList<String>();
for (IdeaPluginDescriptor descriptor : ourPlugins) {
final String version = descriptor.getVersion();
String s = descriptor.getName() + (version != null ? " (" + version + ")" : "");
if (descriptor.isEnabled()) {
if (descriptor.isBundled() || SPECIAL_IDEA_PLUGIN.equals(descriptor.getName())) loadedBundled.add(s);
else loadedCustom.add(s);
}
else {
disabled.add(s);
}
}
Collections.sort(loadedBundled);
Collections.sort(loadedCustom);
Collections.sort(disabled);
getLogger().info("Loaded bundled plugins: " + StringUtil.join(loadedBundled, ", "));
if (!loadedCustom.isEmpty()) {
getLogger().info("Loaded custom plugins: " + StringUtil.join(loadedCustom, ", "));
}
if (!disabled.isEmpty()) {
getLogger().info("Disabled plugins: " + StringUtil.join(disabled, ", "));
}
}
static ClassLoader[] getParentLoaders(Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap, PluginId[] pluginIds) {
if (isUnitTestMode()) return new ClassLoader[0];
final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
for (final PluginId id : pluginIds) {
IdeaPluginDescriptor pluginDescriptor = idToDescriptorMap.get(id);
if (pluginDescriptor == null) {
continue; // Might be an optional dependency
}
final ClassLoader loader = pluginDescriptor.getPluginClassLoader();
if (loader == null) {
getLogger().error("Plugin class loader should be initialized for plugin " + id);
}
classLoaders.add(loader);
}
return classLoaders.toArray(new ClassLoader[classLoaders.size()]);
}
static int countPlugins(String pluginsPath) {
File configuredPluginsDir = new File(pluginsPath);
if (configuredPluginsDir.exists()) {
String[] list = configuredPluginsDir.list();
if (list != null) {
return list.length;
}
}
return 0;
}
static Collection<URL> getClassLoaderUrls() {
final ClassLoader classLoader = PluginManagerCore.class.getClassLoader();
final Class<? extends ClassLoader> aClass = classLoader.getClass();
try {
@SuppressWarnings("unchecked") List<URL> urls = (List<URL>)aClass.getMethod("getUrls").invoke(classLoader);
return urls;
}
catch (IllegalAccessException ignored) { }
catch (InvocationTargetException ignored) { }
catch (NoSuchMethodException ignored) { }
if (classLoader instanceof URLClassLoader) {
return Arrays.asList(((URLClassLoader)classLoader).getURLs());
}
return Collections.emptyList();
}
static void prepareLoadingPluginsErrorMessage(final String errorMessage) {
if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
if (ApplicationManager.getApplication() != null
&& !ApplicationManager.getApplication().isHeadlessEnvironment()
&& !ApplicationManager.getApplication().isUnitTestMode()) {
if (myPluginError == null) {
myPluginError = errorMessage;
}
else {
myPluginError += "\n" + errorMessage;
}
} else {
getLogger().error(errorMessage);
}
}
}
private static void addModulesAsDependents(Map<PluginId, ? super IdeaPluginDescriptorImpl> map) {
for (Map.Entry<String, IdeaPluginDescriptorImpl> entry : ourModulesToContainingPlugins.entrySet()) {
map.put(PluginId.getId(entry.getKey()), entry.getValue());
}
}
static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
if (!builder.isAcyclic()) {
builder.getSCCs().forEach(new TIntProcedure() {
int myTNumber = 0;
public boolean execute(int size) {
if (size > 1) {
for (int j = 0; j < size; j++) {
idToDescriptorMap.get(builder.getNodeByTNumber(myTNumber + j)).setEnabled(false);
}
}
myTNumber += size;
return true;
}
});
}
final Comparator<PluginId> idComparator = builder.comparator();
return new Comparator<IdeaPluginDescriptor>() {
@Override
public int compare(IdeaPluginDescriptor o1, IdeaPluginDescriptor o2) {
final PluginId pluginId1 = o1.getPluginId();
final PluginId pluginId2 = o2.getPluginId();
if (pluginId1.getIdString().equals(CORE_PLUGIN_ID)) return -1;
if (pluginId2.getIdString().equals(CORE_PLUGIN_ID)) return 1;
return idComparator.compare(pluginId1, pluginId2);
}
};
}
private static Graph<PluginId> createPluginIdGraph(final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
final List<PluginId> ids = new ArrayList<PluginId>(idToDescriptorMap.keySet());
// this magic ensures that the dependent plugins always follow their dependencies in lexicographic order
// needed to make sure that extensions are always in the same order
Collections.sort(ids, new Comparator<PluginId>() {
@Override
public int compare(PluginId o1, PluginId o2) {
return o2.getIdString().compareTo(o1.getIdString());
}
});
return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<PluginId>() {
@Override
public Collection<PluginId> getNodes() {
return ids;
}
@Override
public Iterator<PluginId> getIn(PluginId pluginId) {
final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
ArrayList<PluginId> plugins = new ArrayList<PluginId>();
for (PluginId dependentPluginId : descriptor.getDependentPluginIds()) {
// check for missing optional dependency
IdeaPluginDescriptor dep = idToDescriptorMap.get(dependentPluginId);
if (dep != null) {
plugins.add(dep.getPluginId());
}
}
return plugins.iterator();
}
}));
}
@Deprecated
static IdeaPluginDescriptorImpl[] findCorePlugin(IdeaPluginDescriptorImpl[] pluginDescriptors) {
for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
if (CORE_PLUGIN_ID.equals(descriptor.getPluginId().getIdString())) {
return new IdeaPluginDescriptorImpl[] {descriptor};
}
}
return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
}
@Nullable
static IdeaPluginDescriptorImpl loadDescriptorFromDir(final File file, @NonNls String fileName) {
IdeaPluginDescriptorImpl descriptor = null;
File descriptorFile = new File(file, META_INF + File.separator + fileName);
if (descriptorFile.exists()) {
descriptor = new IdeaPluginDescriptorImpl(file);
try {
descriptor.readExternal(descriptorFile.toURI().toURL());
}
catch (Exception e) {
System.err.println("Cannot load: " + descriptorFile.getAbsolutePath());
e.printStackTrace();
}
}
return descriptor;
}
@Nullable
static IdeaPluginDescriptorImpl loadDescriptorFromJar(File file, @NonNls String fileName) {
try {
String fileURL = StringUtil.replace(file.toURI().toASCIIString(), "!", "%21");
URL jarURL = new URL("jar:" + fileURL + "!/META-INF/" + fileName);
ZipFile zipFile = ZipFileCache.acquire(file.getPath());
try {
ZipEntry entry = zipFile.getEntry("META-INF/" + fileName);
if (entry != null) {
Document document = JDOMUtil.loadDocument(zipFile.getInputStream(entry));
IdeaPluginDescriptorImpl descriptor = new IdeaPluginDescriptorImpl(file);
descriptor.readExternal(document, jarURL);
return descriptor;
}
}
finally {
ZipFileCache.release(zipFile);
}
}
catch (XmlSerializationException e) {
getLogger().info("Cannot load " + file, e);
prepareLoadingPluginsErrorMessage("Plugin file " + file.getName() + " contains invalid plugin descriptor file.");
}
catch (Throwable e) {
getLogger().info("Cannot load " + file, e);
}
return null;
}
@Nullable
public static IdeaPluginDescriptorImpl loadDescriptorFromJar(File file) {
return loadDescriptorFromJar(file, PLUGIN_XML);
}
@SuppressWarnings({"HardCodedStringLiteral"})
@Nullable
public static IdeaPluginDescriptorImpl loadDescriptor(final File file, @NonNls final String fileName) {
IdeaPluginDescriptorImpl descriptor = null;
if (file.isDirectory()) {
descriptor = loadDescriptorFromDir(file, fileName);
if (descriptor == null) {
File libDir = new File(file, "lib");
if (!libDir.isDirectory()) {
return null;
}
final File[] files = libDir.listFiles();
if (files == null || files.length == 0) {
return null;
}
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (o2.getName().startsWith(file.getName())) return Integer.MAX_VALUE;
if (o1.getName().startsWith(file.getName())) return -Integer.MAX_VALUE;
if (o2.getName().startsWith("resources")) return -Integer.MAX_VALUE;
if (o1.getName().startsWith("resources")) return Integer.MAX_VALUE;
return 0;
}
});
for (final File f : files) {
if (FileUtil.isJarOrZip(f)) {
descriptor = loadDescriptorFromJar(f, fileName);
if (descriptor != null) {
descriptor.setPath(file);
break;
}
// getLogger().warn("Cannot load descriptor from " + f.getName() + "");
}
else if (f.isDirectory()) {
IdeaPluginDescriptorImpl descriptor1 = loadDescriptorFromDir(f, fileName);
if (descriptor1 != null) {
if (descriptor != null) {
getLogger().info("Cannot load " + file + " because two or more plugin.xml's detected");
return null;
}
descriptor = descriptor1;
descriptor.setPath(file);
}
}
}
}
}
else if (StringUtil.endsWithIgnoreCase(file.getName(), ".jar") && file.exists()) {
descriptor = loadDescriptorFromJar(file, fileName);
}
if (descriptor != null && descriptor.getOptionalConfigs() != null && !descriptor.getOptionalConfigs().isEmpty()) {
final Map<PluginId, IdeaPluginDescriptorImpl> descriptors = new HashMap<PluginId, IdeaPluginDescriptorImpl>(descriptor.getOptionalConfigs().size());
for (Map.Entry<PluginId, String> entry: descriptor.getOptionalConfigs().entrySet()) {
String optionalDescriptorName = entry.getValue();
assert !Comparing.equal(fileName, optionalDescriptorName) : "recursive dependency: "+ fileName;
IdeaPluginDescriptorImpl optionalDescriptor = loadDescriptor(file, optionalDescriptorName);
if (optionalDescriptor == null && !FileUtil.isJarOrZip(file)) {
for (URL url : getClassLoaderUrls()) {
if ("file".equals(url.getProtocol())) {
optionalDescriptor = loadDescriptor(new File(decodeUrl(url.getFile())), optionalDescriptorName);
if (optionalDescriptor != null) {
break;
}
}
}
}
if (optionalDescriptor != null) {
descriptors.put(entry.getKey(), optionalDescriptor);
}
else {
getLogger().info("Cannot find optional descriptor " + optionalDescriptorName);
}
}
descriptor.setOptionalDescriptors(descriptors);
}
return descriptor;
}
public static void loadDescriptors(String pluginsPath,
List<IdeaPluginDescriptorImpl> result,
@Nullable StartupProgress progress,
int pluginsCount) {
final File pluginsHome = new File(pluginsPath);
final File[] files = pluginsHome.listFiles();
if (files != null) {
int i = result.size();
for (File file : files) {
final IdeaPluginDescriptorImpl descriptor = loadDescriptor(file, PLUGIN_XML);
if (descriptor == null) continue;
if (progress != null) {
progress.showProgress(descriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * ((float)++i / pluginsCount));
}
int oldIndex = result.indexOf(descriptor);
if (oldIndex >= 0) {
final IdeaPluginDescriptorImpl oldDescriptor = result.get(oldIndex);
if (StringUtil.compareVersionNumbers(oldDescriptor.getVersion(), descriptor.getVersion()) < 0) {
result.set(oldIndex, descriptor);
}
}
else {
result.add(descriptor);
}
}
}
}
@Nullable
static String filterBadPlugins(List<? extends IdeaPluginDescriptor> result, final Map<String, String> disabledPluginNames) {
final Map<PluginId, IdeaPluginDescriptor> idToDescriptorMap = new HashMap<PluginId, IdeaPluginDescriptor>();
final StringBuffer message = new StringBuffer();
boolean pluginsWithoutIdFound = false;
for (Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
final IdeaPluginDescriptor descriptor = it.next();
final PluginId id = descriptor.getPluginId();
if (id == null) {
pluginsWithoutIdFound = true;
}
if (idToDescriptorMap.containsKey(id)) {
message.append("<br>");
message.append(IdeBundle.message("message.duplicate.plugin.id"));
message.append(id);
it.remove();
}
else if (descriptor.isEnabled()) {
idToDescriptorMap.put(id, descriptor);
}
}
addModulesAsDependents(idToDescriptorMap);
final List<String> disabledPluginIds = new ArrayList<String>();
final LinkedHashSet<String> faultyDescriptors = new LinkedHashSet<String>();
for (final Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
final IdeaPluginDescriptor pluginDescriptor = it.next();
checkDependants(pluginDescriptor, new Function<PluginId, IdeaPluginDescriptor>() {
@Override
public IdeaPluginDescriptor fun(final PluginId pluginId) {
return idToDescriptorMap.get(pluginId);
}
}, new Condition<PluginId>() {
@Override
public boolean value(final PluginId pluginId) {
if (!idToDescriptorMap.containsKey(pluginId)) {
pluginDescriptor.setEnabled(false);
if (!pluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX)) {
faultyDescriptors.add(pluginId.getIdString());
disabledPluginIds.add(pluginDescriptor.getPluginId().getIdString());
message.append("<br>");
final String name = pluginDescriptor.getName();
final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
String pluginName;
if (descriptor == null) {
pluginName = pluginId.getIdString();
if (disabledPluginNames.containsKey(pluginName)) {
pluginName = disabledPluginNames.get(pluginName);
}
}
else {
pluginName = descriptor.getName();
}
message.append(getDisabledPlugins().contains(pluginId.getIdString())
? IdeBundle.message("error.required.plugin.disabled", name, pluginName)
: IdeBundle.message("error.required.plugin.not.installed", name, pluginName));
}
it.remove();
return false;
}
return true;
}
});
}
if (!disabledPluginIds.isEmpty()) {
myPlugins2Disable = disabledPluginIds;
myPlugins2Enable = faultyDescriptors;
message.append("<br>");
message.append("<br>").append("<a href=\"" + DISABLE + "\">Disable ");
if (disabledPluginIds.size() == 1) {
final PluginId pluginId2Disable = PluginId.getId(disabledPluginIds.iterator().next());
message.append(idToDescriptorMap.containsKey(pluginId2Disable) ? idToDescriptorMap.get(pluginId2Disable).getName() : pluginId2Disable.getIdString());
}
else {
message.append("not loaded plugins");
}
message.append("</a>");
boolean possibleToEnable = true;
for (String descriptor : faultyDescriptors) {
if (disabledPluginNames.get(descriptor) == null) {
possibleToEnable = false;
break;
}
}
if (possibleToEnable) {
message.append("<br>").append("<a href=\"" + ENABLE + "\">Enable ").append(faultyDescriptors.size() == 1 ? disabledPluginNames.get(faultyDescriptors.iterator().next()) : " all necessary plugins").append("</a>");
}
message.append("<br>").append("<a href=\"" + EDIT + "\">Open plugin manager</a>");
}
if (pluginsWithoutIdFound) {
message.append("<br>");
message.append(IdeBundle.message("error.plugins.without.id.found"));
}
if (message.length() > 0) {
message.insert(0, IdeBundle.message("error.problems.found.loading.plugins"));
return message.toString();
}
return "";
}
static void loadDescriptorsFromClassPath(@NotNull List<IdeaPluginDescriptorImpl> result, @Nullable StartupProgress progress) {
Collection<URL> urls = getClassLoaderUrls();
String platformPrefix = System.getProperty(PlatformUtilsCore.PLATFORM_PREFIX_KEY);
int i = 0;
for (URL url : urls) {
i++;
if ("file".equals(url.getProtocol())) {
File file = new File(decodeUrl(url.getFile()));
IdeaPluginDescriptorImpl platformPluginDescriptor = null;
if (platformPrefix != null) {
platformPluginDescriptor = loadDescriptor(file, platformPrefix + "Plugin.xml");
if (platformPluginDescriptor != null && !result.contains(platformPluginDescriptor)) {
platformPluginDescriptor.setUseCoreClassLoader(true);
result.add(platformPluginDescriptor);
}
}
IdeaPluginDescriptorImpl pluginDescriptor = loadDescriptor(file, PLUGIN_XML);
if (platformPrefix != null && pluginDescriptor != null && pluginDescriptor.getName().equals(SPECIAL_IDEA_PLUGIN)) {
continue;
}
if (pluginDescriptor != null && !result.contains(pluginDescriptor)) {
if (platformPluginDescriptor != null) {
// if we found a regular plugin.xml in the same .jar/root as a platform-prefixed descriptor, use the core loader for it too
pluginDescriptor.setUseCoreClassLoader(true);
}
result.add(pluginDescriptor);
if (progress != null) {
progress.showProgress("Plugin loaded: " + pluginDescriptor.getName(), PLUGINS_PROGRESS_MAX_VALUE * ((float)i / urls.size()));
}
}
}
}
}
@SuppressWarnings("deprecation")
private static String decodeUrl(String file) {
String quotePluses = StringUtil.replace(file, "+", "%2B");
return URLDecoder.decode(quotePluses);
}
static void loadDescriptorsFromProperty(final List<IdeaPluginDescriptorImpl> result) {
final String pathProperty = System.getProperty(PROPERTY_PLUGIN_PATH);
if (pathProperty == null) return;
for (StringTokenizer t = new StringTokenizer(pathProperty, File.pathSeparator + ","); t.hasMoreTokens();) {
String s = t.nextToken();
final IdeaPluginDescriptorImpl ideaPluginDescriptor = loadDescriptor(new File(s), PLUGIN_XML);
if (ideaPluginDescriptor != null) {
result.add(ideaPluginDescriptor);
}
}
}
public static IdeaPluginDescriptorImpl[] loadDescriptors(@Nullable StartupProgress progress) {
if (ClassUtilCore.isLoadingOfExternalPluginsDisabled()) {
return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
}
final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
int pluginsCount = countPlugins(PathManager.getPluginsPath()) + countPlugins(PathManager.getPreInstalledPluginsPath());
loadDescriptors(PathManager.getPluginsPath(), result, progress, pluginsCount);
Application application = ApplicationManager.getApplication();
boolean fromSources = false;
if (application == null || !application.isUnitTestMode()) {
int size = result.size();
loadDescriptors(PathManager.getPreInstalledPluginsPath(), result, progress, pluginsCount);
fromSources = size == result.size();
}
loadDescriptorsFromProperty(result);
loadDescriptorsFromClassPath(result, fromSources ? progress : null);
IdeaPluginDescriptorImpl[] pluginDescriptors = result.toArray(new IdeaPluginDescriptorImpl[result.size()]);
final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new com.intellij.util.containers.HashMap<PluginId, IdeaPluginDescriptorImpl>();
for (final IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
}
Arrays.sort(pluginDescriptors, getPluginDescriptorComparator(idToDescriptorMap));
return pluginDescriptors;
}
static void mergeOptionalConfigs(Map<PluginId, IdeaPluginDescriptorImpl> descriptors) {
final Map<PluginId, IdeaPluginDescriptorImpl> descriptorsWithModules = new HashMap<PluginId, IdeaPluginDescriptorImpl>(descriptors);
addModulesAsDependents(descriptorsWithModules);
for (IdeaPluginDescriptorImpl descriptor : descriptors.values()) {
final Map<PluginId, IdeaPluginDescriptorImpl> optionalDescriptors = descriptor.getOptionalDescriptors();
if (optionalDescriptors != null && !optionalDescriptors.isEmpty()) {
for (Map.Entry<PluginId, IdeaPluginDescriptorImpl> entry: optionalDescriptors.entrySet()) {
if (descriptorsWithModules.containsKey(entry.getKey())) {
descriptor.mergeOptionalConfig(entry.getValue());
}
}
}
}
}
public static void initClassLoader(@NotNull ClassLoader parentLoader, @NotNull IdeaPluginDescriptorImpl descriptor) {
final List<File> classPath = descriptor.getClassPath();
final ClassLoader loader =
createPluginClassLoader(classPath.toArray(new File[classPath.size()]), new ClassLoader[]{parentLoader}, descriptor);
descriptor.setLoader(loader);
}
static BuildNumber getBuildNumber() {
if (ourBuildNumber == null) {
ourBuildNumber = BuildNumber.fromString(System.getProperty("idea.plugins.compatible.build"));
if (ourBuildNumber == null) {
ourBuildNumber = BUILD_NUMBER == null ? null : BuildNumber.fromString(BUILD_NUMBER);
if (ourBuildNumber == null) {
ourBuildNumber = BuildNumber.fallback();
}
}
}
return ourBuildNumber;
}
static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor, IdeaPluginDescriptor[] loaded) {
final String idString = descriptor.getPluginId().getIdString();
if (CORE_PLUGIN_ID.equals(idString)) {
return false;
}
//noinspection HardCodedStringLiteral
final String pluginId = System.getProperty("idea.load.plugins.id");
if (pluginId == null) {
if (descriptor instanceof IdeaPluginDescriptorImpl && !descriptor.isEnabled()) return true;
if (!shouldLoadPlugins()) return true;
}
final List<String> pluginIds = pluginId == null ? null : StringUtil.split(pluginId, ",");
final boolean checkModuleDependencies = !ourModulesToContainingPlugins.isEmpty() && !ourModulesToContainingPlugins.containsKey("com.intellij.modules.all");
if (checkModuleDependencies && !hasModuleDependencies(descriptor)) {
return true;
}
boolean shouldLoad;
//noinspection HardCodedStringLiteral
final String loadPluginCategory = System.getProperty("idea.load.plugins.category");
if (loadPluginCategory != null) {
shouldLoad = loadPluginCategory.equals(descriptor.getCategory());
}
else {
if (pluginIds != null) {
shouldLoad = pluginIds.contains(idString);
if (!shouldLoad) {
Map<PluginId,IdeaPluginDescriptor> map = new HashMap<PluginId, IdeaPluginDescriptor>();
for (final IdeaPluginDescriptor pluginDescriptor : loaded) {
map.put(pluginDescriptor.getPluginId(), pluginDescriptor);
}
addModulesAsDependents(map);
for (String id : pluginIds) {
final IdeaPluginDescriptor descriptorFromProperty = map.get(PluginId.getId(id));
if (descriptorFromProperty != null && isDependent(descriptorFromProperty, descriptor.getPluginId(), map, checkModuleDependencies)) {
shouldLoad = true;
break;
}
}
}
} else {
shouldLoad = !getDisabledPlugins().contains(idString);
}
if (shouldLoad && descriptor instanceof IdeaPluginDescriptorImpl) {
if (isIncompatible(descriptor)) return true;
}
}
return !shouldLoad;
}
public static boolean isIncompatible(final IdeaPluginDescriptor descriptor) {
return isIncompatible(descriptor, getBuildNumber());
}
public static boolean isIncompatible(final IdeaPluginDescriptor descriptor, @Nullable BuildNumber buildNumber) {
if (buildNumber == null) {
buildNumber = getBuildNumber();
}
try {
if (!StringUtil.isEmpty(descriptor.getSinceBuild())) {
BuildNumber sinceBuild = BuildNumber.fromString(descriptor.getSinceBuild(), descriptor.getName());
if (sinceBuild.compareTo(buildNumber) > 0) {
return true;
}
}
if (!StringUtil.isEmpty(descriptor.getUntilBuild()) && !buildNumber.isSnapshot()) {
BuildNumber untilBuild = BuildNumber.fromString(descriptor.getUntilBuild(), descriptor.getName());
if (untilBuild.compareTo(buildNumber) < 0) {
return true;
}
}
}
catch (RuntimeException ignored) { }
return false;
}
public static boolean shouldSkipPlugin(final IdeaPluginDescriptor descriptor) {
if (descriptor instanceof IdeaPluginDescriptorImpl) {
IdeaPluginDescriptorImpl descriptorImpl = (IdeaPluginDescriptorImpl)descriptor;
Boolean skipped = descriptorImpl.getSkipped();
if (skipped != null) {
return skipped.booleanValue();
}
boolean result = shouldSkipPlugin(descriptor, ourPlugins) || isBrokenPlugin(descriptor);
descriptorImpl.setSkipped(result);
return result;
}
return shouldSkipPlugin(descriptor, ourPlugins) || isBrokenPlugin(descriptor);
}
static void initializePlugins(@Nullable StartupProgress progress) {
configureExtensions();
final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress);
final Class callerClass = ReflectionUtil.findCallerClass(1);
assert callerClass != null;
final ClassLoader parentLoader = callerClass.getClassLoader();
final List<IdeaPluginDescriptorImpl> result = new ArrayList<IdeaPluginDescriptorImpl>();
final HashMap<String, String> disabledPluginNames = new HashMap<String, String>();
List<String> brokenPluginsList = new ArrayList<String>();
for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
boolean skipped = shouldSkipPlugin(descriptor, pluginDescriptors);
if (!skipped) {
if (isBrokenPlugin(descriptor)) {
brokenPluginsList.add(descriptor.getName());
skipped = true;
}
}
if (!skipped) {
final List<String> modules = descriptor.getModules();
if (modules != null) {
for (String module : modules) {
if (!ourModulesToContainingPlugins.containsKey(module)) {
ourModulesToContainingPlugins.put(module, descriptor);
}
}
}
result.add(descriptor);
}
else {
descriptor.setEnabled(false);
disabledPluginNames.put(descriptor.getPluginId().getIdString(), descriptor.getName());
initClassLoader(parentLoader, descriptor);
}
}
String errorMessage = filterBadPlugins(result, disabledPluginNames);
if (!brokenPluginsList.isEmpty()) {
if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
errorMessage += "<br>";
}
errorMessage += "Following plugins are incompatible with current IDE build: " + StringUtil.join(brokenPluginsList, ", ")
+ "<br>\n" + StringUtil.notNullize(errorMessage);
}
final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new HashMap<PluginId, IdeaPluginDescriptorImpl>();
for (final IdeaPluginDescriptorImpl descriptor : result) {
idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
}
final IdeaPluginDescriptor corePluginDescriptor = idToDescriptorMap.get(PluginId.getId(CORE_PLUGIN_ID));
assert corePluginDescriptor != null : CORE_PLUGIN_ID + " not found; platform prefix is " + System.getProperty(PlatformUtilsCore.PLATFORM_PREFIX_KEY);
for (IdeaPluginDescriptorImpl descriptor : result) {
if (descriptor != corePluginDescriptor) {
descriptor.insertDependency(corePluginDescriptor);
}
}
mergeOptionalConfigs(idToDescriptorMap);
addModulesAsDependents(idToDescriptorMap);
final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
if (!builder.isAcyclic()) {
if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
errorMessage += "<br>";
}
final String cyclePresentation;
if (ApplicationManager.getApplication().isInternal()) {
final List<String> cycles = new ArrayList<String>();
builder.getSCCs().forEach(new TIntProcedure() {
int myTNumber = 0;
public boolean execute(int size) {
if (size > 1) {
String cycle = "";
for (int j = 0; j < size; j++) {
cycle += builder.getNodeByTNumber(myTNumber + j).getIdString() + " ";
}
cycles.add(cycle);
}
myTNumber += size;
return true;
}
});
cyclePresentation = ": " + StringUtil.join(cycles, ";");
} else {
final Couple<PluginId> circularDependency = builder.getCircularDependency();
final PluginId id = circularDependency.getFirst();
final PluginId parentId = circularDependency.getSecond();
cyclePresentation = id + "->" + parentId + "->...->" + id;
}
errorMessage += IdeBundle.message("error.plugins.should.not.have.cyclic.dependencies") + cyclePresentation;
}
prepareLoadingPluginsErrorMessage(errorMessage);
final Comparator<PluginId> idComparator = builder.comparator();
// sort descriptors according to plugin dependencies
Collections.sort(result, new Comparator<IdeaPluginDescriptor>() {
@Override
public int compare(IdeaPluginDescriptor o1, IdeaPluginDescriptor o2) {
return idComparator.compare(o1.getPluginId(), o2.getPluginId());
}
});
for (int i = 0; i < result.size(); i++) {
ourId2Index.put(result.get(i).getPluginId(), i);
}
int i = 0;
for (final IdeaPluginDescriptorImpl pluginDescriptor : result) {
if (pluginDescriptor.getPluginId().getIdString().equals(CORE_PLUGIN_ID) || pluginDescriptor.isUseCoreClassLoader()) {
pluginDescriptor.setLoader(parentLoader);
}
else {
final List<File> classPath = pluginDescriptor.getClassPath();
final PluginId[] dependentPluginIds = pluginDescriptor.getDependentPluginIds();
final ClassLoader[] parentLoaders = getParentLoaders(idToDescriptorMap, dependentPluginIds);
final ClassLoader pluginClassLoader = createPluginClassLoader(classPath.toArray(new File[classPath.size()]),
parentLoaders.length > 0 ? parentLoaders : new ClassLoader[] {parentLoader},
pluginDescriptor);
pluginDescriptor.setLoader(pluginClassLoader);
}
if (progress != null) {
progress.showProgress("", PLUGINS_PROGRESS_MAX_VALUE + (i++ / (float)result.size()) * 0.35f);
}
}
registerExtensionPointsAndExtensions(Extensions.getRootArea(), result);
Extensions.getRootArea().getExtensionPoint(Extensions.AREA_LISTENER_EXTENSION_POINT).registerExtension(new AreaListener() {
@Override
public void areaCreated(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
registerExtensionPointsAndExtensions(Extensions.getArea(areaInstance), result);
}
@Override
public void areaDisposing(@NotNull String areaClass, @NotNull AreaInstance areaInstance) {
}
});
ourPlugins = pluginDescriptors;
}
private static void registerExtensionPointsAndExtensions(ExtensionsArea area, List<IdeaPluginDescriptorImpl> loadedPlugins) {
for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
descriptor.registerExtensionPoints(area);
}
Set<String> epNames = ContainerUtil.newHashSet();
for (ExtensionPoint point : area.getExtensionPoints()) {
epNames.add(point.getName());
}
for (IdeaPluginDescriptorImpl descriptor : loadedPlugins) {
for (String epName : epNames) {
descriptor.registerExtensions(area, epName);
}
}
}
public static void initPlugins(@Nullable StartupProgress progress) {
long start = System.currentTimeMillis();
try {
initializePlugins(progress);
}
catch (RuntimeException e) {
getLogger().error(e);
throw e;
}
getLogger().info(ourPlugins.length + " plugins initialized in " + (System.currentTimeMillis() - start) + " ms");
logPlugins();
ClassUtilCore.clearJarURLCache();
}
private static class LoggerHolder {
private static final Logger ourLogger = Logger.getInstance("#com.intellij.ide.plugins.PluginManager");
}
private static class IdeaLogProvider implements LogProvider {
@Override
public void error(String message) {
getLogger().error(message);
}
@Override
public void error(String message, Throwable t) {
getLogger().error(message, t);
}
@Override
public void error(Throwable t) {
getLogger().error(t);
}
@Override
public void warn(String message) {
getLogger().info(message);
}
@Override
public void warn(String message, Throwable t) {
getLogger().info(message, t);
}
@Override
public void warn(Throwable t) {
getLogger().info(t);
}
}
}