add runAsync
[idea/community.git] / platform / analysis-impl / src / com / intellij / profile / codeInspection / ProjectInspectionProfileManager.kt
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.profile.codeInspection
17
18 import com.intellij.codeInspection.InspectionProfile
19 import com.intellij.codeInspection.ex.InspectionProfileImpl
20 import com.intellij.codeInspection.ex.InspectionToolRegistrar
21 import com.intellij.concurrency.runAsync
22 import com.intellij.configurationStore.SchemeDataHolder
23 import com.intellij.openapi.Disposable
24 import com.intellij.openapi.application.ApplicationManager
25 import com.intellij.openapi.components.PersistentStateComponent
26 import com.intellij.openapi.components.State
27 import com.intellij.openapi.components.Storage
28 import com.intellij.openapi.options.SchemeManager
29 import com.intellij.openapi.options.SchemeManagerFactory
30 import com.intellij.openapi.project.Project
31 import com.intellij.openapi.project.ProjectManager
32 import com.intellij.openapi.project.ProjectManagerListener
33 import com.intellij.openapi.startup.StartupActivity
34 import com.intellij.openapi.util.Disposer
35 import com.intellij.openapi.util.JDOMUtil
36 import com.intellij.openapi.util.text.StringUtil
37 import com.intellij.packageDependencies.DependencyValidationManager
38 import com.intellij.profile.Profile
39 import com.intellij.psi.search.scope.packageSet.NamedScopeManager
40 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder
41 import com.intellij.util.ui.UIUtil
42 import com.intellij.util.xmlb.Accessor
43 import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters
44 import com.intellij.util.xmlb.XmlSerializer
45 import org.jdom.Element
46 import java.util.concurrent.CompletableFuture
47 import java.util.function.Function
48
49 const val PROFILE = "profile"
50
51 private const val VERSION = "1.0"
52 private const val SCOPE = "scope"
53 private const val NAME = "name"
54 private const val PROJECT_DEFAULT_PROFILE_NAME = "Project Default"
55
56 @State(name = "InspectionProjectProfileManager", storages = arrayOf(Storage(value = "inspectionProfiles/profiles_settings.xml", exclusive = true)))
57 class ProjectInspectionProfileManager(val project: Project,
58                                       private val applicationProfileManager: InspectionProfileManager,
59                                       private val scopeManager: DependencyValidationManager,
60                                       private val localScopesHolder: NamedScopeManager,
61                                       schemeManagerFactory: SchemeManagerFactory) : BaseInspectionProfileManager(project.messageBus), PersistentStateComponent<Element> {
62   companion object {
63     @JvmStatic
64     fun getInstanceImpl(project: Project): ProjectInspectionProfileManager {
65       return InspectionProjectProfileManager.getInstance(project) as ProjectInspectionProfileManager
66     }
67   }
68
69   private var scopeListener: NamedScopesHolder.ScopeListener? = null
70
71   private var state = State()
72
73   private val initialLoadSchemesFuture: CompletableFuture<*>
74
75   private val skipDefaultsSerializationFilter = object : SkipDefaultValuesSerializationFilters(State()) {
76     override fun accepts(accessor: Accessor, bean: Any, beanValue: Any?): Boolean {
77       if (beanValue == null && accessor.name == "projectProfile") {
78         return false
79       }
80       return super.accepts(accessor, bean, beanValue)
81     }
82   }
83
84   override val schemeManager: SchemeManager<InspectionProfileImpl>
85
86   private data class State(@field:com.intellij.util.xmlb.annotations.OptionTag("PROJECT_PROFILE") var projectProfile: String? = PROJECT_DEFAULT_PROFILE_NAME,
87                            @field:com.intellij.util.xmlb.annotations.OptionTag("USE_PROJECT_PROFILE") var useProjectProfile: Boolean = true)
88
89   init {
90     schemeManager = schemeManagerFactory.create("inspectionProfiles", object : InspectionProfileProcessor() {
91       override fun createScheme(dataHolder: SchemeDataHolder<InspectionProfileImpl>, name: String, attributeProvider: Function<String, String?>, duringLoad: Boolean): InspectionProfileImpl {
92         val profile = InspectionProfileImpl(name, InspectionToolRegistrar.getInstance(), this@ProjectInspectionProfileManager, InspectionProfileImpl.getDefaultProfile(), dataHolder)
93         profile.isProjectLevel = true
94         return profile
95       }
96
97       override fun isSchemeFile(name: CharSequence) = !StringUtil.equals(name, "profiles_settings.xml")
98
99       override fun onSchemeDeleted(scheme: InspectionProfileImpl) {
100         schemeRemoved(scheme)
101       }
102
103       override fun onSchemeAdded(scheme: InspectionProfileImpl) {
104         if (scheme.wasInitialized()) {
105           fireProfileChanged(scheme)
106         }
107       }
108
109       override fun onCurrentSchemeSwitched(oldScheme: InspectionProfileImpl?, newScheme: InspectionProfileImpl?) {
110         for (adapter in profileListeners) {
111           adapter.profileActivated(oldScheme, newScheme)
112         }
113       }
114     }, isUseOldFileNameSanitize = true)
115
116     val app = ApplicationManager.getApplication()
117     if (app.isUnitTestMode) {
118       initialLoadSchemesFuture = CompletableFuture.completedFuture(null)
119     }
120     else {
121       initialLoadSchemesFuture = runAsync { schemeManager.loadSchemes() }
122     }
123
124     project.messageBus.connect().subscribe(ProjectManager.TOPIC, object: ProjectManagerListener {
125       override fun projectClosed(project: Project) {
126         val cleanupInspectionProfilesRunnable = {
127           cleanupSchemes(project)
128           (InspectionProfileManager.getInstance() as BaseInspectionProfileManager).cleanupSchemes(project)
129           fireProfilesShutdown()
130         }
131
132         if (app.isUnitTestMode || app.isHeadlessEnvironment) {
133           cleanupInspectionProfilesRunnable.invoke()
134         }
135         else {
136           app.executeOnPooledThread(cleanupInspectionProfilesRunnable)
137         }
138       }
139     })
140   }
141
142   fun isCurrentProfileInitialized() = currentProfile.wasInitialized()
143
144   @Synchronized override fun updateProfile(profile: Profile) {
145     super.updateProfile(profile)
146
147     (profile as InspectionProfileImpl).initInspectionTools(project)
148   }
149
150   override fun schemeRemoved(scheme: InspectionProfile) {
151     scheme.cleanup(project)
152   }
153
154   @Suppress("unused")
155   private class ProjectInspectionProfileStartUpActivity : StartupActivity {
156     override fun runActivity(project: Project) {
157       getInstanceImpl(project).apply {
158         initialLoadSchemesFuture.thenAccept {
159           currentProfile.initInspectionTools(project)
160           fireProfilesInitialized()
161
162           val app = ApplicationManager.getApplication()
163           if (app.isUnitTestMode && app.isDispatchThread) {
164             // do not restart daemon in the middle of the test
165             //noinspection TestOnlyProblems
166             UIUtil.dispatchAllInvocationEvents()
167           }
168         }
169
170         scopeListener = NamedScopesHolder.ScopeListener {
171           for (profile in schemeManager.allSchemes) {
172             profile.scopesChanged()
173           }
174         }
175
176         scopeManager.addScopeListener(scopeListener!!)
177         localScopesHolder.addScopeListener(scopeListener!!)
178         Disposer.register(project, Disposable {
179           scopeManager.removeScopeListener(scopeListener!!)
180           localScopesHolder.removeScopeListener(scopeListener!!)
181         })
182       }
183     }
184   }
185
186   @Synchronized override fun loadState(state: Element) {
187     val data = state.getChild("settings")
188
189     val newState = State()
190
191     data?.let {
192       try {
193         severityRegistrar.readExternal(it)
194       }
195       catch (e: Throwable) {
196         LOG.error(e)
197       }
198
199       XmlSerializer.deserializeInto(newState, it)
200     }
201
202     this.state = newState
203
204     if (data != null && data.getChild("version")?.getAttributeValue("value") != VERSION) {
205       for (o in data.getChildren("option")) {
206         if (o.getAttributeValue("name") == "USE_PROJECT_LEVEL_SETTINGS") {
207           if (o.getAttributeValue("value").toBoolean()) {
208             if (newState.projectProfile != null) {
209               currentProfile.convert(data, project)
210             }
211           }
212           break
213         }
214       }
215     }
216
217     if (newState.useProjectProfile) {
218       schemeManager.currentSchemeName = newState.projectProfile
219     }
220   }
221
222   @Synchronized override fun getState(): Element? {
223     val result = Element("settings")
224     val state = this.state
225     state.projectProfile = schemeManager.currentSchemeName
226     XmlSerializer.serializeInto(state, result, skipDefaultsSerializationFilter)
227     if (!result.children.isEmpty()) {
228       result.addContent(Element("version").setAttribute("value", VERSION))
229     }
230
231     severityRegistrar.writeExternal(result)
232     if (JDOMUtil.isEmpty(result)) {
233       result.name = "state"
234       return result
235     }
236     else {
237       return Element("state").addContent(result)
238     }
239   }
240
241   override fun getScopesManager() = scopeManager
242
243   @Synchronized override fun getProfiles(): Collection<Profile> {
244     currentProfile
245     return schemeManager.allSchemes
246   }
247
248   @Synchronized override fun getAvailableProfileNames(): Array<String> = schemeManager.allSchemeNames.toTypedArray()
249
250   val projectProfile: String?
251     get() = schemeManager.currentSchemeName
252
253   @Synchronized override fun setRootProfile(name: String?) {
254     if (name != schemeManager.currentSchemeName) {
255       schemeManager.currentSchemeName = name
256       state.useProjectProfile = name != null
257     }
258   }
259
260   @Synchronized fun setCurrentProfile(profile: InspectionProfileImpl?) {
261     schemeManager.setCurrent(profile)
262     state.useProjectProfile = profile != null
263   }
264
265   @Synchronized override fun getCurrentProfile(): InspectionProfileImpl {
266     if (!state.useProjectProfile) {
267       return applicationProfileManager.currentProfile as InspectionProfileImpl
268     }
269
270     var currentScheme = schemeManager.currentScheme
271     if (currentScheme == null) {
272       currentScheme = schemeManager.allSchemes.firstOrNull()
273       if (currentScheme == null) {
274         currentScheme = InspectionProfileImpl(PROJECT_DEFAULT_PROFILE_NAME, InspectionToolRegistrar.getInstance(), this,
275                                               InspectionProfileImpl.getDefaultProfile(), null)
276         currentScheme.copyFrom(applicationProfileManager.currentProfile)
277         currentScheme.isProjectLevel = true
278         currentScheme.setName(PROJECT_DEFAULT_PROFILE_NAME)
279         schemeManager.addScheme(currentScheme)
280       }
281       schemeManager.setCurrent(currentScheme, false)
282     }
283     return currentScheme
284   }
285
286   private fun fireProfilesInitialized() {
287     for (listener in profileListeners) {
288       listener.profilesInitialized()
289     }
290   }
291
292   private fun fireProfilesShutdown() {
293     for (profileChangeAdapter in profileListeners) {
294       profileChangeAdapter.profilesShutdown()
295     }
296   }
297
298   @Synchronized override fun getProfile(name: String, returnRootProfileIfNamedIsAbsent: Boolean): Profile? {
299     val profile = schemeManager.findSchemeByName(name)
300     return profile ?: applicationProfileManager.getProfile(name, returnRootProfileIfNamedIsAbsent)
301   }
302 }