get rid of intellij.build.toolbox.litegen parameter and use BuildOptions.TOOLBOX_LITE...
[idea/community.git] / platform / core-impl / src / com / intellij / ide / plugins / IdeaPluginDescriptorImpl.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.ide.plugins;
3
4 import com.intellij.AbstractBundle;
5 import com.intellij.CommonBundle;
6 import com.intellij.diagnostic.PluginException;
7 import com.intellij.openapi.application.Application;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.components.ComponentConfig;
10 import com.intellij.openapi.components.OldComponentConfig;
11 import com.intellij.openapi.components.ServiceDescriptor;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.extensions.ExtensionPoint;
14 import com.intellij.openapi.extensions.Extensions;
15 import com.intellij.openapi.extensions.ExtensionsArea;
16 import com.intellij.openapi.extensions.PluginId;
17 import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
18 import com.intellij.openapi.extensions.impl.ExtensionsAreaImpl;
19 import com.intellij.openapi.util.*;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.util.text.StringUtilRt;
22 import com.intellij.util.ObjectUtils;
23 import com.intellij.util.SmartList;
24 import com.intellij.util.containers.ContainerUtil;
25 import com.intellij.util.containers.HashSetInterner;
26 import com.intellij.util.containers.Interner;
27 import com.intellij.util.containers.MultiMap;
28 import com.intellij.util.xmlb.BeanBinding;
29 import com.intellij.util.xmlb.JDOMXIncluder;
30 import com.intellij.util.xmlb.XmlSerializer;
31 import org.jdom.Content;
32 import org.jdom.Element;
33 import org.jdom.JDOMException;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 import org.picocontainer.MutablePicoContainer;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.net.MalformedURLException;
41 import java.net.URL;
42 import java.text.ParseException;
43 import java.text.SimpleDateFormat;
44 import java.util.*;
45 import java.util.regex.Matcher;
46 import java.util.regex.Pattern;
47
48 /**
49  * @author mike
50  */
51 public class IdeaPluginDescriptorImpl implements IdeaPluginDescriptor {
52   public static final IdeaPluginDescriptorImpl[] EMPTY_ARRAY = new IdeaPluginDescriptorImpl[0];
53
54   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.plugins.PluginDescriptor");
55
56   private static final String APPLICATION_SERVICE = "com.intellij.applicationService";
57   private static final String PROJECT_SERVICE = "com.intellij.projectService";
58   private static final String MODULE_SERVICE = "com.intellij.moduleService";
59
60   @SuppressWarnings("SSBasedInspection")
61   public static final List<String> SERVICE_QUALIFIED_ELEMENT_NAMES = Arrays.asList(APPLICATION_SERVICE, PROJECT_SERVICE, MODULE_SERVICE);
62
63   private final File myPath;
64   private final boolean myBundled;
65   private String myName;
66   private PluginId myId;
67   private final NullableLazyValue<String> myDescription = NullableLazyValue.createValue(() -> computeDescription());
68   private @Nullable String myProductCode;
69   private @Nullable Date myReleaseDate;
70   private int myReleaseVersion;
71   private String myResourceBundleBaseName;
72   private String myChangeNotes;
73   private String myVersion;
74   private String myVendor;
75   private String myVendorEmail;
76   private String myVendorUrl;
77   private String myVendorLogoPath;
78   private String myCategory;
79   private String myUrl;
80   private PluginId[] myDependencies = PluginId.EMPTY_ARRAY;
81   private PluginId[] myOptionalDependencies = PluginId.EMPTY_ARRAY;
82   private Map<PluginId, List<String>> myOptionalConfigs;
83   private Map<PluginId, List<IdeaPluginDescriptorImpl>> myOptionalDescriptors;
84   private @Nullable List<Element> myActionElements;
85   private @Nullable List<ComponentConfig> myAppComponents;
86   private @Nullable List<ComponentConfig> myProjectComponents;
87   private @Nullable List<ComponentConfig> myModuleComponents;
88   private @Nullable MultiMap<String, Element> myExtensions;  // extension point name -> list of extension elements
89   private @Nullable List<ServiceDescriptor> myAppServices;
90   private @Nullable List<ServiceDescriptor> myProjectServices;
91   private @Nullable List<ServiceDescriptor> myModuleServices;
92   private @Nullable MultiMap<String, Element> myExtensionsPoints;
93   private List<String> myModules;
94   private ClassLoader myLoader;
95   private String myDescriptionChildText;
96   private boolean myUseIdeaClassLoader;
97   private boolean myUseCoreClassLoader;
98   private boolean myAllowBundledUpdate;
99   private boolean myImplementationDetail;
100   private String mySinceBuild;
101   private String myUntilBuild;
102
103   private boolean myEnabled = true;
104   private boolean myDeleted;
105   private Boolean mySkipped;
106
107   public IdeaPluginDescriptorImpl(@NotNull File pluginPath, boolean bundled) {
108     myPath = pluginPath;
109     myBundled = bundled;
110   }
111
112   @Override
113   public File getPath() {
114     return myPath;
115   }
116
117   public void readExternal(@NotNull Element element,
118                            @NotNull URL url,
119                            @NotNull JDOMXIncluder.PathResolver pathResolver,
120                            @Nullable Interner<String> stringInterner) throws InvalidDataException, MalformedURLException {
121     Application app = ApplicationManager.getApplication();
122     readExternal(element, url, app != null && app.isUnitTestMode(), pathResolver, stringInterner);
123   }
124
125   public void loadFromFile(@NotNull File file, @Nullable SafeJdomFactory factory) throws IOException, JDOMException {
126     Application app = ApplicationManager.getApplication();
127     readExternal(JDOMUtil.load(file, factory), file.toURI().toURL(), app != null && app.isUnitTestMode(),
128                  JDOMXIncluder.DEFAULT_PATH_RESOLVER, factory == null ? null : factory.stringInterner());
129   }
130
131   private void readExternal(@NotNull Element element,
132                             @NotNull URL url,
133                             boolean ignoreMissingInclude,
134                             @NotNull JDOMXIncluder.PathResolver pathResolver,
135                             @Nullable Interner<String> stringInterner) throws InvalidDataException, MalformedURLException {
136     // root element always `!isIncludeElement` and it means that result always is a singleton list
137     // (also, plugin xml describes one plugin, this descriptor is not able to represent several plugins)
138     if (JDOMUtil.isEmpty(element)) {
139       return;
140     }
141
142     JDOMXIncluder.resolveNonXIncludeElement(element, url, ignoreMissingInclude, pathResolver);
143     readExternal(element, stringInterner);
144   }
145
146   // used in upsource
147   protected void readExternal(@NotNull Element element, @Nullable Interner<String> stringInterner) {
148     OptimizedPluginBean pluginBean = XmlSerializer.deserialize(element, OptimizedPluginBean.class);
149     myUrl = pluginBean.url;
150
151     String idString = StringUtil.nullize(pluginBean.id, true);
152     String nameString = StringUtil.nullize(pluginBean.name, true);
153     myId = idString != null ? PluginId.getId(idString) : nameString != null ? PluginId.getId(nameString) : null;
154     myName = ObjectUtils.chooseNotNull(nameString, idString);
155
156     ProductDescriptor pd = pluginBean.productDescriptor;
157     myProductCode = pd != null? pd.code : null;
158     myReleaseDate = parseReleaseDate(pluginBean);
159     myReleaseVersion = pd != null? pd.releaseVersion : 0;
160
161     String internalVersionString = pluginBean.formatVersion;
162     if (internalVersionString != null) {
163       try {
164         Integer.parseInt(internalVersionString);
165       }
166       catch (NumberFormatException e) {
167         LOG.error(new PluginException("Invalid value in plugin.xml format version: '" + internalVersionString + "'", e, myId));
168       }
169     }
170     myUseIdeaClassLoader = pluginBean.useIdeaClassLoader;
171     myAllowBundledUpdate = pluginBean.allowBundledUpdate;
172     myImplementationDetail = pluginBean.implementationDetail;
173     if (pluginBean.ideaVersion != null) {
174       mySinceBuild = pluginBean.ideaVersion.sinceBuild;
175       myUntilBuild = convertExplicitBigNumberInUntilBuildToStar(pluginBean.ideaVersion.untilBuild);
176     }
177
178     myResourceBundleBaseName = pluginBean.resourceBundle;
179
180     myDescriptionChildText = pluginBean.description;
181     myChangeNotes = pluginBean.changeNotes;
182     myVersion = pluginBean.pluginVersion;
183     if (myVersion == null) {
184       myVersion = PluginManagerCore.getBuildNumber().asStringWithoutProductCode();
185     }
186
187     myCategory = pluginBean.category;
188
189     if (pluginBean.vendor != null) {
190       myVendor = pluginBean.vendor.name;
191       myVendorEmail = pluginBean.vendor.email;
192       myVendorUrl = pluginBean.vendor.url;
193       myVendorLogoPath = pluginBean.vendor.logo;
194     }
195
196     // preserve items order as specified in xml (filterBadPlugins will not fail if module comes first)
197     Set<PluginId> dependentPlugins = new LinkedHashSet<>();
198     Set<PluginId> nonOptionalDependentPlugins = new LinkedHashSet<>();
199     if (pluginBean.dependencies != null) {
200       myOptionalConfigs = new LinkedHashMap<>();
201       for (PluginDependency dependency : pluginBean.dependencies) {
202         String text = dependency.pluginId;
203         if (!StringUtil.isEmptyOrSpaces(text)) {
204           PluginId id = PluginId.getId(text);
205           dependentPlugins.add(id);
206           if (dependency.optional) {
207             if (!StringUtil.isEmptyOrSpaces(dependency.configFile)) {
208               myOptionalConfigs.computeIfAbsent(id, it -> new SmartList<>()).add(dependency.configFile);
209             }
210           }
211           else {
212             nonOptionalDependentPlugins.add(id);
213           }
214         }
215       }
216     }
217
218     myDependencies = dependentPlugins.isEmpty() ? PluginId.EMPTY_ARRAY : dependentPlugins.toArray(PluginId.EMPTY_ARRAY);
219     if (nonOptionalDependentPlugins.size() == dependentPlugins.size()) {
220       myOptionalDependencies = PluginId.EMPTY_ARRAY;
221     }
222     else {
223       myOptionalDependencies = ContainerUtil.filter(dependentPlugins, id -> !nonOptionalDependentPlugins.contains(id)).toArray(PluginId.EMPTY_ARRAY);
224     }
225
226     // we cannot use our new kotlin-aware XmlSerializer, so, will be used different bean cache,
227     // but it is not a problem because in any case new XmlSerializer is not used for our core classes (plugin bean, component config and so on).
228     Ref<BeanBinding> oldComponentConfigBeanBinding = new Ref<>();
229
230     // only for CoreApplicationEnvironment
231     if (stringInterner == null) {
232       stringInterner = new HashSetInterner<>(SERVICE_QUALIFIED_ELEMENT_NAMES);
233     }
234
235     MultiMap<String, Element> extensions = myExtensions;
236     for (Content content : element.getContent()) {
237       if (!(content instanceof Element)) {
238         continue;
239       }
240
241       Element child = (Element)content;
242       switch (child.getName()) {
243         case "extensions": {
244           String ns = child.getAttributeValue("defaultExtensionNs");
245           for (Element extensionElement : child.getChildren()) {
246             String os = extensionElement.getAttributeValue("os");
247             if (os != null) {
248               extensionElement.removeAttribute("os");
249               if (!isComponentSuitableForOs(os)) {
250                 continue;
251               }
252             }
253
254             String qualifiedExtensionPointName = stringInterner.intern(ExtensionsAreaImpl.extractPointName(extensionElement, ns));
255             List<ServiceDescriptor> services;
256             if (qualifiedExtensionPointName.equals(APPLICATION_SERVICE)) {
257               if (myAppServices == null) {
258                 myAppServices = new ArrayList<>();
259               }
260               services = myAppServices;
261             }
262             else if (qualifiedExtensionPointName.equals(PROJECT_SERVICE)) {
263               if (myProjectServices == null) {
264                 myProjectServices = new ArrayList<>();
265               }
266               services = myProjectServices;
267             }
268             else if (qualifiedExtensionPointName.equals(MODULE_SERVICE)) {
269               if (myModuleServices == null) {
270                 myModuleServices = new ArrayList<>();
271               }
272               services = myModuleServices;
273             }
274             else {
275               if (extensions == null) {
276                 extensions = MultiMap.createSmart();
277                 myExtensions = extensions;
278               }
279               extensions.putValue(qualifiedExtensionPointName, extensionElement);
280               continue;
281             }
282
283             services.add(readServiceDescriptor(extensionElement));
284           }
285         }
286         break;
287
288         case "extensionPoints": {
289           if (myExtensionsPoints == null) {
290             myExtensionsPoints = MultiMap.createSmart();
291           }
292           for (Element extensionPoint : child.getChildren()) {
293             myExtensionsPoints.putValue(StringUtilRt.notNullize(extensionPoint.getAttributeValue(ExtensionsAreaImpl.ATTRIBUTE_AREA)), extensionPoint);
294           }
295         }
296         break;
297
298         case "actions": {
299           if (myActionElements == null) {
300             myActionElements = new ArrayList<>(child.getChildren());
301           }
302           else {
303             myActionElements.addAll(child.getChildren());
304           }
305         }
306         break;
307
308         case "module": {
309           String moduleName = child.getAttributeValue("value");
310           if (moduleName != null) {
311             if (myModules == null) {
312               myModules = new SmartList<>();
313             }
314             myModules.add(moduleName);
315           }
316         }
317         break;
318
319         case OptimizedPluginBean.APPLICATION_COMPONENTS: {
320           // because of x-pointer, maybe several application-components tag in document
321           if (myAppComponents == null) {
322             myAppComponents = new ArrayList<>();
323           }
324           readComponents(child, oldComponentConfigBeanBinding, (ArrayList<ComponentConfig>)myAppComponents);
325         }
326         break;
327
328         case OptimizedPluginBean.PROJECT_COMPONENTS: {
329           if (myProjectComponents == null) {
330             myProjectComponents = new ArrayList<>();
331           }
332           readComponents(child, oldComponentConfigBeanBinding, (ArrayList<ComponentConfig>)myProjectComponents);
333         }
334         break;
335
336         case OptimizedPluginBean.MODULE_COMPONENTS: {
337           if (myModuleComponents == null) {
338             myModuleComponents = new ArrayList<>();
339           }
340           readComponents(child, oldComponentConfigBeanBinding, (ArrayList<ComponentConfig>)myModuleComponents);
341         }
342         break;
343       }
344
345       child.getContent().clear();
346     }
347   }
348
349   @NotNull
350   private static ServiceDescriptor readServiceDescriptor(@NotNull Element element) {
351     ServiceDescriptor descriptor = new ServiceDescriptor();
352     descriptor.serviceInterface = element.getAttributeValue("serviceInterface");
353     descriptor.serviceImplementation = element.getAttributeValue("serviceImplementation");
354     descriptor.testServiceImplementation = element.getAttributeValue("testServiceImplementation");
355     descriptor.configurationSchemaKey = element.getAttributeValue("configurationSchemaKey");
356     descriptor.overrides = Boolean.parseBoolean(element.getAttributeValue("overrides"));
357     return descriptor;
358   }
359
360   private static void readComponents(@NotNull Element parent, @NotNull Ref<BeanBinding> oldComponentConfigBean, @NotNull ArrayList<? super ComponentConfig> result) {
361     List<Content> content = parent.getContent();
362     int contentSize = content.size();
363     if (contentSize == 0) {
364       return;
365     }
366
367     result.ensureCapacity(result.size() + contentSize);
368
369     for (Content child : content) {
370       if (!(child instanceof Element)) {
371         continue;
372       }
373
374       Element componentElement = ((Element)child);
375       if (!componentElement.getName().equals("component")) {
376         continue;
377       }
378
379       OldComponentConfig componentConfig = new OldComponentConfig();
380
381       BeanBinding beanBinding = oldComponentConfigBean.get();
382       if (beanBinding == null) {
383         beanBinding = XmlSerializer.getBeanBinding(componentConfig);
384         oldComponentConfigBean.set(beanBinding);
385       }
386
387       beanBinding.deserializeInto(componentConfig, componentElement);
388       Map<String, String> options = componentConfig.options;
389       if (options != null && (!isComponentSuitableForOs(options.get("os")))) {
390         continue;
391       }
392
393       result.add(componentConfig);
394     }
395   }
396
397   @Nullable
398   private static Date parseReleaseDate(@NotNull OptimizedPluginBean bean) {
399     final ProductDescriptor pd = bean.productDescriptor;
400     final String dateStr = pd != null? pd.releaseDate : null;
401     if (dateStr != null) {
402       try {
403         return new SimpleDateFormat("yyyyMMdd", Locale.US).parse(dateStr);
404       }
405       catch (ParseException e) {
406         LOG.info("Error parse release date from plugin descriptor for plugin " + bean.name + " {" + bean.id + "}: " + e.getMessage());
407       }
408     }
409     return null;
410   }
411
412   public static final Pattern EXPLICIT_BIG_NUMBER_PATTERN = Pattern.compile("(.*)\\.(9{4,}+|10{4,}+)");
413
414   /**
415    * Convert build number like '146.9999' to '146.*' (like plugin repository does) to ensure that plugins which have such values in
416    * 'until-build' attribute will be compatible with 146.SNAPSHOT build.
417    */
418   public static String convertExplicitBigNumberInUntilBuildToStar(@Nullable String build) {
419     if (build == null) return null;
420     Matcher matcher = EXPLICIT_BIG_NUMBER_PATTERN.matcher(build);
421     if (matcher.matches()) {
422       return matcher.group(1) + ".*";
423     }
424     return build;
425   }
426
427   public void registerExtensionPoints(@NotNull ExtensionsArea area) {
428     if (myExtensionsPoints != null) {
429       for (Element element : myExtensionsPoints.get(StringUtil.notNullize(area.getAreaClass()))) {
430         area.registerExtensionPoint(this, element);
431       }
432     }
433   }
434
435   void registerExtensions(@NotNull ExtensionPointImpl<?>[] extensionPoints, @NotNull MutablePicoContainer picoContainer) {
436     if (myExtensions == null) {
437       return;
438     }
439
440     for (ExtensionPointImpl<?> extensionPoint : extensionPoints) {
441       extensionPoint.createAndRegisterAdapters(myExtensions.get(extensionPoint.getName()), this, picoContainer);
442     }
443   }
444
445   // made public for Upsource
446   public void registerExtensions(@NotNull ExtensionsArea area, @NotNull ExtensionPoint<?> extensionPoint) {
447     if (myExtensions == null) {
448       return;
449     }
450     ((ExtensionPointImpl)extensionPoint).createAndRegisterAdapters(myExtensions.get(extensionPoint.getName()), this, area.getPicoContainer());
451   }
452
453   @Override
454   public String getDescription() {
455     return myDescription.getValue();
456   }
457
458   @Override
459   public String getChangeNotes() {
460     return myChangeNotes;
461   }
462
463   @Override
464   public String getName() {
465     return myName;
466   }
467
468   @Nullable
469   @Override
470   public String getProductCode() {
471     return myProductCode;
472   }
473
474   @Nullable
475   @Override
476   public Date getReleaseDate() {
477     return myReleaseDate;
478   }
479
480   @Override
481   public int getReleaseVersion() {
482     return myReleaseVersion;
483   }
484
485   @Override
486   @NotNull
487   public PluginId[] getDependentPluginIds() {
488     return myDependencies;
489   }
490
491   @Override
492   @NotNull
493   public PluginId[] getOptionalDependentPluginIds() {
494     return myOptionalDependencies;
495   }
496
497   @Override
498   public String getVendor() {
499     return myVendor;
500   }
501
502   @Override
503   public String getVersion() {
504     return myVersion;
505   }
506
507   @Override
508   public String getResourceBundleBaseName() {
509     return myResourceBundleBaseName;
510   }
511
512   @Override
513   public String getCategory() {
514     return myCategory;
515   }
516
517   /*
518      This setter was explicitly defined to be able to set a category for a
519      descriptor outside its loading from the xml file.
520      Problem was that most commonly plugin authors do not publish the plugin's
521      category in its .xml file so to be consistent in plugins representation
522      (e.g. in the Plugins form) we have to set this value outside.
523   */
524   public void setCategory(String category) {
525     myCategory = category;
526   }
527
528   @SuppressWarnings("UnusedDeclaration") // Used in Upsource
529   @Nullable
530   public MultiMap<String, Element> getExtensionsPoints() {
531     return myExtensionsPoints;
532   }
533
534   @SuppressWarnings("UnusedDeclaration") // Used in Upsource
535   @Nullable
536   public MultiMap<String, Element> getExtensions() {
537     if (myExtensions == null) return null;
538     MultiMap<String, Element> result = MultiMap.create();
539     result.putAllValues(myExtensions);
540     return result;
541   }
542
543   @SuppressWarnings("HardCodedStringLiteral")
544   @NotNull
545   public List<File> getClassPath() {
546     if (myPath.isDirectory()) {
547       final List<File> result = new ArrayList<>();
548       final File classesDir = new File(myPath, "classes");
549
550       if (classesDir.exists()) {
551         result.add(classesDir);
552       }
553
554       final File[] files = new File(myPath, "lib").listFiles();
555       if (files != null && files.length > 0) {
556         for (final File f : files) {
557           if (f.isFile()) {
558             final String name = f.getName();
559             if (StringUtil.endsWithIgnoreCase(name, ".jar") || StringUtil.endsWithIgnoreCase(name, ".zip")) {
560               result.add(f);
561             }
562           }
563           else {
564             result.add(f);
565           }
566         }
567       }
568
569       return result;
570     }
571     else {
572       return Collections.singletonList(myPath);
573     }
574   }
575
576   @Override
577   @Nullable
578   public List<Element> getAndClearActionDescriptionElements() {
579     List<Element> result = myActionElements;
580     myActionElements = null;
581     return result;
582   }
583
584   @Override
585   @NotNull
586   public List<ComponentConfig> getAppComponents() {
587     return ContainerUtil.notNullize(myAppComponents);
588   }
589
590   @Override
591   @NotNull
592   public List<ComponentConfig> getProjectComponents() {
593     return ContainerUtil.notNullize(myProjectComponents);
594   }
595
596   @Override
597   @NotNull
598   public List<ComponentConfig> getModuleComponents() {
599     return ContainerUtil.notNullize(myModuleComponents);
600   }
601
602   @NotNull
603   public List<ServiceDescriptor> getAppServices() {
604     return ContainerUtil.notNullize(myAppServices);
605   }
606
607   @NotNull
608   public List<ServiceDescriptor> getProjectServices() {
609     return ContainerUtil.notNullize(myProjectServices);
610   }
611
612   @NotNull
613   public List<ServiceDescriptor> getModuleServices() {
614     return ContainerUtil.notNullize(myModuleServices);
615   }
616
617   @Override
618   public String getVendorEmail() {
619     return myVendorEmail;
620   }
621
622   @Override
623   public String getVendorUrl() {
624     return myVendorUrl;
625   }
626
627   @Override
628   public String getUrl() {
629     return myUrl;
630   }
631
632   public void setUrl(String val) {
633     myUrl = val;
634   }
635
636   public boolean isDeleted() {
637     return myDeleted;
638   }
639
640   public void setDeleted(boolean deleted) {
641     myDeleted = deleted;
642   }
643
644   public void setLoader(ClassLoader loader) {
645     myLoader = loader;
646   }
647
648   @Override
649   public PluginId getPluginId() {
650     return myId;
651   }
652
653   /** @deprecated doesn't make sense for installed plugins; use PluginNode#getDownloads (to be removed in IDEA 2019) */
654   @Override
655   @Deprecated
656   public String getDownloads() {
657     return null;
658   }
659
660   @Override
661   public ClassLoader getPluginClassLoader() {
662     return myLoader != null ? myLoader : getClass().getClassLoader();
663   }
664
665   @Override
666   public String getVendorLogoPath() {
667     return myVendorLogoPath;
668   }
669
670   @Override
671   public boolean getUseIdeaClassLoader() {
672     return myUseIdeaClassLoader;
673   }
674
675   boolean isUseCoreClassLoader() {
676     return myUseCoreClassLoader;
677   }
678
679   void setUseCoreClassLoader(@SuppressWarnings("SameParameterValue") boolean useCoreClassLoader) {
680     myUseCoreClassLoader = useCoreClassLoader;
681   }
682
683   private String computeDescription() {
684     ResourceBundle bundle = null;
685     if (myResourceBundleBaseName != null) {
686       try {
687         bundle = AbstractBundle.getResourceBundle(myResourceBundleBaseName, getPluginClassLoader());
688       }
689       catch (MissingResourceException e) {
690         LOG.info("Cannot find plugin " + myId + " resource-bundle: " + myResourceBundleBaseName);
691       }
692     }
693
694     if (bundle == null) {
695       return myDescriptionChildText;
696     }
697
698     return CommonBundle.messageOrDefault(bundle, "plugin." + myId + ".description", StringUtil.notNullize(myDescriptionChildText));
699   }
700
701   void insertDependency(@NotNull IdeaPluginDescriptor d) {
702     PluginId[] deps = new PluginId[getDependentPluginIds().length + 1];
703     deps[0] = d.getPluginId();
704     System.arraycopy(myDependencies, 0, deps, 1, deps.length - 1);
705     myDependencies = deps;
706   }
707
708   @Override
709   public boolean isEnabled() {
710     return myEnabled;
711   }
712
713   @Override
714   public void setEnabled(final boolean enabled) {
715     myEnabled = enabled;
716   }
717
718   @Override
719   public String getSinceBuild() {
720     return mySinceBuild;
721   }
722
723   @Override
724   public String getUntilBuild() {
725     return myUntilBuild;
726   }
727
728   Map<PluginId, List<String>> getOptionalConfigs() {
729     return myOptionalConfigs;
730   }
731
732   @Nullable
733   Map<PluginId, List<IdeaPluginDescriptorImpl>> getOptionalDescriptors() {
734     return myOptionalDescriptors;
735   }
736
737   void setOptionalDescriptors(@Nullable Map<PluginId, List<IdeaPluginDescriptorImpl>> optionalDescriptors) {
738     myOptionalDescriptors = optionalDescriptors;
739   }
740
741   void mergeOptionalConfig(@NotNull IdeaPluginDescriptorImpl descriptor) {
742     if (myExtensions == null) {
743       myExtensions = descriptor.myExtensions;
744     }
745     else if (descriptor.myExtensions != null) {
746       myExtensions.putAllValues(descriptor.myExtensions);
747     }
748
749     if (myExtensionsPoints == null) {
750       myExtensionsPoints = descriptor.myExtensionsPoints;
751     }
752     else if (descriptor.myExtensionsPoints != null) {
753       myExtensionsPoints.putAllValues(descriptor.myExtensionsPoints);
754     }
755
756     if (myActionElements == null) {
757       myActionElements = descriptor.myActionElements;
758     }
759     else if (descriptor.myActionElements != null) {
760       myActionElements.addAll(descriptor.myActionElements);
761     }
762
763     myAppComponents = concatOrNull(myAppComponents, descriptor.myAppComponents);
764     myProjectComponents = concatOrNull(myProjectComponents, descriptor.myProjectComponents);
765     myModuleComponents = concatOrNull(myModuleComponents, descriptor.myModuleComponents);
766
767     myAppServices = concatOrNull(myAppServices, descriptor.myAppServices);
768     myProjectServices = concatOrNull(myProjectServices, descriptor.myProjectServices);
769     myModuleServices = concatOrNull(myModuleServices, descriptor.myModuleServices);
770   }
771
772   @Nullable
773   private static <T> List<T> concatOrNull(@Nullable List<T> l1, @Nullable List<T> l2) {
774     if (l1 == null) {
775       return l2;
776     }
777     else if (l2 == null) {
778       return l1;
779     }
780     else {
781       return ContainerUtil.concat(l1, l2);
782     }
783   }
784
785   public Boolean getSkipped() {
786     return mySkipped;
787   }
788
789   public void setSkipped(final Boolean skipped) {
790     mySkipped = skipped;
791   }
792
793   @Override
794   public boolean isBundled() {
795     return myBundled;
796   }
797
798   @Override
799   public boolean allowBundledUpdate() {
800     return myAllowBundledUpdate;
801   }
802
803   @Override
804   public boolean isImplementationDetail() {
805     return myImplementationDetail;
806   }
807
808   @NotNull
809   public List<String> getModules() {
810     return ContainerUtil.notNullize(myModules);
811   }
812
813   @Override
814   public boolean equals(Object o) {
815     return this == o || o instanceof IdeaPluginDescriptorImpl && myId == ((IdeaPluginDescriptorImpl)o).myId;
816   }
817
818   @Override
819   public int hashCode() {
820     return Objects.hashCode(myId);
821   }
822
823   @Override
824   public String toString() {
825     return "PluginDescriptor(name=" + myName + ", classpath=" + myPath + ")";
826   }
827
828   private static boolean isComponentSuitableForOs(@Nullable String os) {
829     if (StringUtil.isEmpty(os)) {
830       return true;
831     }
832
833     if (os.equals(Extensions.OS.mac.name())) {
834       return SystemInfoRt.isMac;
835     }
836     else if (os.equals(Extensions.OS.linux.name())) {
837       return SystemInfoRt.isLinux;
838     }
839     else if (os.equals(Extensions.OS.windows.name())) {
840       return SystemInfoRt.isWindows;
841     }
842     else if (os.equals(Extensions.OS.unix.name())) {
843       return SystemInfoRt.isUnix;
844     }
845     else if (os.equals(Extensions.OS.freebsd.name())) {
846       return SystemInfoRt.isFreeBSD;
847     }
848     else {
849       throw new IllegalArgumentException("Unknown OS '" + os + "'");
850     }
851   }
852 }