2 * Copyright 2000-2015 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.
16 package com.intellij.ide.actions
18 import com.intellij.AbstractBundle
19 import com.intellij.CommonBundle
20 import com.intellij.configurationStore.ROOT_CONFIG
21 import com.intellij.configurationStore.SchemeManagerFactoryBase
22 import com.intellij.configurationStore.path
23 import com.intellij.configurationStore.sortByDeprecated
24 import com.intellij.ide.IdeBundle
25 import com.intellij.ide.plugins.IdeaPluginDescriptor
26 import com.intellij.ide.plugins.PluginManager
27 import com.intellij.ide.plugins.PluginManagerCore
28 import com.intellij.openapi.actionSystem.AnAction
29 import com.intellij.openapi.actionSystem.AnActionEvent
30 import com.intellij.openapi.application.ApplicationManager
31 import com.intellij.openapi.application.PathManager
32 import com.intellij.openapi.application.impl.ApplicationImpl
33 import com.intellij.openapi.components.*
34 import com.intellij.openapi.components.impl.ServiceManagerImpl
35 import com.intellij.openapi.components.impl.stores.StateStorageManager
36 import com.intellij.openapi.components.impl.stores.StoreUtil
37 import com.intellij.openapi.diagnostic.Logger
38 import com.intellij.openapi.extensions.PluginDescriptor
39 import com.intellij.openapi.options.OptionsBundle
40 import com.intellij.openapi.options.SchemesManagerFactory
41 import com.intellij.openapi.project.DumbAware
42 import com.intellij.openapi.ui.Messages
43 import com.intellij.openapi.util.io.FileUtilRt
44 import com.intellij.openapi.vfs.CharsetToolkit
45 import com.intellij.util.*
46 import com.intellij.util.containers.putValue
47 import com.intellij.util.io.ZipUtil
48 import gnu.trove.THashMap
49 import gnu.trove.THashSet
50 import java.io.IOException
51 import java.io.OutputStream
52 import java.io.OutputStreamWriter
53 import java.nio.file.Path
54 import java.nio.file.Paths
56 import java.util.zip.ZipEntry
57 import java.util.zip.ZipOutputStream
59 private class ExportSettingsAction : AnAction(), DumbAware {
60 override fun actionPerformed(e: AnActionEvent?) {
61 ApplicationManager.getApplication().saveSettings()
63 val dialog = ChooseComponentsToExportDialog(getExportableComponentsMap(true, true), true,
64 IdeBundle.message("title.select.components.to.export"),
66 "prompt.please.check.all.components.to.export"))
67 if (!dialog.showAndGet()) {
71 val markedComponents = dialog.exportableComponents
72 if (markedComponents.isEmpty()) {
76 val exportFiles = THashSet<Path>()
77 for (markedComponent in markedComponents) {
78 exportFiles.addAll(markedComponent.files)
81 val saveFile = dialog.exportFile
83 if (saveFile.exists() && Messages.showOkCancelDialog(
84 IdeBundle.message("prompt.overwrite.settings.file", saveFile.toString()),
85 IdeBundle.message("title.file.already.exists"), Messages.getWarningIcon()) != Messages.OK) {
89 exportSettings(exportFiles, saveFile.outputStream(), FileUtilRt.toSystemIndependentName(PathManager.getConfigPath()))
90 ShowFilePathAction.showDialog(AnAction.getEventProject(e), IdeBundle.message("message.settings.exported.successfully"),
91 IdeBundle.message("title.export.successful"), saveFile.toFile(), null)
93 catch (e1: IOException) {
94 Messages.showErrorDialog(IdeBundle.message("error.writing.settings", e1.toString()), IdeBundle.message("title.error.writing.file"))
99 // not internal only to test
100 fun exportSettings(exportFiles: Set<Path>, out: OutputStream, configPath: String) {
101 val zipOut = MyZipOutputStream(out)
103 val writtenItemRelativePaths = THashSet<String>()
104 for (file in exportFiles) {
106 val relativePath = FileUtilRt.getRelativePath(configPath, file.toAbsolutePath().systemIndependentPath, '/')!!
107 ZipUtil.addFileOrDirRecursively(zipOut, null, file.toFile(), relativePath, null, writtenItemRelativePaths)
111 exportInstalledPlugins(zipOut)
113 val zipEntry = ZipEntry(ImportSettingsFilenameFilter.SETTINGS_JAR_MARKER)
114 zipOut.putNextEntry(zipEntry)
122 private class MyZipOutputStream(out: OutputStream) : ZipOutputStream(out) {
123 override fun close() {
131 data class ExportableItem(val files: List<Path>, val presentableName: String, val roamingType: RoamingType = RoamingType.DEFAULT)
133 private val LOG = Logger.getInstance(ExportSettingsAction::class.java)
135 private fun exportInstalledPlugins(zipOut: MyZipOutputStream) {
136 val plugins = ArrayList<String>()
137 for (descriptor in PluginManagerCore.getPlugins()) {
138 if (!descriptor.isBundled && descriptor.isEnabled) {
139 plugins.add(descriptor.pluginId.idString)
142 if (plugins.isEmpty()) {
146 val e = ZipEntry(PluginManager.INSTALLED_TXT)
147 zipOut.putNextEntry(e)
149 PluginManagerCore.writePluginsList(plugins, OutputStreamWriter(zipOut, CharsetToolkit.UTF8_CHARSET))
156 // onlyPaths - include only specified paths (relative to config dir, ends with "/" if directory)
157 fun getExportableComponentsMap(onlyExisting: Boolean,
158 computePresentableNames: Boolean,
159 storageManager: StateStorageManager = ApplicationManager.getApplication().stateStore.stateStorageManager,
160 onlyPaths: Set<String>? = null): Map<Path, List<ExportableItem>> {
161 val result = LinkedHashMap<Path, MutableList<ExportableItem>>()
162 val processor = { component: ExportableComponent ->
163 val item = ExportableItem(component.exportFiles.map { it.toPath() }, component.presentableName, RoamingType.DEFAULT)
164 for (exportFile in item.files) {
165 result.putValue(exportFile, item)
169 @Suppress("DEPRECATION")
170 ApplicationManager.getApplication().getComponents(ExportableApplicationComponent::class.java).forEach(processor)
171 ServiceBean.loadServicesFromBeans(ExportableComponent.EXTENSION_POINT, ExportableComponent::class.java).forEach(processor)
173 val configPath = storageManager.expandMacros(ROOT_CONFIG)
175 fun isSkipFile(file: Path): Boolean {
176 if (onlyPaths != null) {
177 var relativePath = FileUtilRt.getRelativePath(configPath, file.systemIndependentPath, '/')!!
178 if (!file.fileName.toString().contains('.') && !file.isFile()) {
181 if (!onlyPaths.contains(relativePath)) {
186 return onlyExisting && !file.exists()
189 if (onlyExisting || onlyPaths != null) {
190 result.keys.removeAll(::isSkipFile)
193 val fileToContent = THashMap<Path, String>()
195 ServiceManagerImpl.processAllImplementationClasses(ApplicationManager.getApplication() as ApplicationImpl, PairProcessor<Class<*>, PluginDescriptor> { aClass, pluginDescriptor ->
196 val stateAnnotation = StoreUtil.getStateSpec(aClass)
197 if (stateAnnotation == null || stateAnnotation.name.isNullOrEmpty() || ExportableComponent::class.java.isAssignableFrom(aClass)) {
198 return@PairProcessor true
201 val storage = stateAnnotation.storages.sortByDeprecated().firstOrNull() ?: return@PairProcessor true
202 if (!(storage.roamingType != RoamingType.DISABLED && storage.storageClass == StateStorage::class && !storage.path.isNullOrEmpty())) {
203 return@PairProcessor true
206 var additionalExportFile: Path? = null
207 var additionalExportPath = stateAnnotation.additionalExportFile
208 if (additionalExportPath.isNotEmpty()) {
209 // backward compatibility - path can contain macro
210 if (additionalExportPath[0] == '$') {
211 additionalExportFile = Paths.get(storageManager.expandMacros(additionalExportPath))
214 additionalExportFile = Paths.get(storageManager.expandMacros(ROOT_CONFIG), additionalExportPath)
216 if (isSkipFile(additionalExportFile)) {
217 additionalExportFile = null
221 val file = Paths.get(storageManager.expandMacros(storage.path))
222 val isFileIncluded = !isSkipFile(file)
223 if (isFileIncluded || additionalExportFile != null) {
224 if (computePresentableNames && onlyExisting && additionalExportFile == null && file.fileName.toString().endsWith(".xml")) {
225 val content = fileToContent.getOrPut(file) { file.readText() }
226 if (!content.contains("""<component name="${stateAnnotation.name}">""")) {
227 return@PairProcessor true
231 val files = if (additionalExportFile == null) listOf(file) else if (isFileIncluded) listOf(file, additionalExportFile) else listOf(additionalExportFile)
232 val item = ExportableItem(files, if (computePresentableNames) getComponentPresentableName(stateAnnotation, aClass, pluginDescriptor) else "", storage.roamingType)
233 result.putValue(file, item)
234 if (additionalExportFile != null) {
235 result.putValue(additionalExportFile, item)
241 // must be in the end - because most of SchemeManager clients specify additionalExportFile in the State spec
242 (SchemesManagerFactory.getInstance() as SchemeManagerFactoryBase).process {
243 if (it.roamingType != RoamingType.DISABLED && it.presentableName != null && it.fileSpec.getOrNull(0) != '$') {
244 val file = Paths.get(storageManager.expandMacros(ROOT_CONFIG), it.fileSpec)
245 if (!result.containsKey(file) && !isSkipFile(file)) {
246 result.putValue(file, ExportableItem(listOf(file), it.presentableName, it.roamingType))
253 private fun getComponentPresentableName(state: State, aClass: Class<*>, pluginDescriptor: PluginDescriptor?): String {
254 val presentableName = state.presentableName.java
255 if (presentableName != State.NameGetter::class.java) {
257 return ReflectionUtil.newInstance(presentableName).get()
259 catch (e: Exception) {
264 val defaultName = state.name
266 fun trimDefaultName() = defaultName.removeSuffix("Settings")
268 var resourceBundleName: String?
269 if (pluginDescriptor is IdeaPluginDescriptor && "com.intellij" != pluginDescriptor.pluginId.idString) {
270 resourceBundleName = pluginDescriptor.resourceBundleBaseName
271 if (resourceBundleName == null) {
272 if (pluginDescriptor.vendor == "JetBrains") {
273 resourceBundleName = OptionsBundle.PATH_TO_BUNDLE
276 return trimDefaultName()
281 resourceBundleName = OptionsBundle.PATH_TO_BUNDLE
284 var classLoader = pluginDescriptor?.pluginClassLoader ?: aClass.classLoader
285 if (classLoader != null) {
286 val message = messageOrDefault(classLoader, resourceBundleName, defaultName)
287 if (message !== defaultName) {
291 if (PlatformUtils.isRubyMine()) {
292 // ruby plugin in RubyMine has id "com.intellij", so, we cannot set "resource-bundle" in plugin.xml
293 return messageOrDefault(classLoader, "org.jetbrains.plugins.ruby.RBundle", defaultName)
296 return trimDefaultName()
299 private fun messageOrDefault(classLoader: ClassLoader, bundleName: String, defaultName: String): String {
300 val bundle = AbstractBundle.getResourceBundle(bundleName, classLoader) ?: return defaultName
301 return CommonBundle.messageOrDefault(bundle, "exportable.$defaultName.presentable.name", defaultName)