2 * Copyright 2000-2016 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.profile.codeInspection
18 import com.intellij.codeInspection.InspectionProfile
19 import com.intellij.codeInspection.ex.InspectionProfileImpl
20 import com.intellij.codeInspection.ex.InspectionToolRegistrar
21 import com.intellij.configurationStore.*
22 import com.intellij.openapi.Disposable
23 import com.intellij.openapi.application.ApplicationManager
24 import com.intellij.openapi.components.PersistentStateComponent
25 import com.intellij.openapi.components.State
26 import com.intellij.openapi.components.Storage
27 import com.intellij.openapi.options.SchemeManagerFactory
28 import com.intellij.openapi.project.Project
29 import com.intellij.openapi.project.ProjectManager
30 import com.intellij.openapi.project.ProjectManagerListener
31 import com.intellij.openapi.startup.StartupActivity
32 import com.intellij.openapi.util.Disposer
33 import com.intellij.openapi.util.text.StringUtil
34 import com.intellij.packageDependencies.DependencyValidationManager
35 import com.intellij.profile.Profile
36 import com.intellij.project.isDirectoryBased
37 import com.intellij.psi.search.scope.packageSet.NamedScopeManager
38 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder
39 import com.intellij.util.loadElement
40 import com.intellij.util.ui.UIUtil
41 import com.intellij.util.xmlb.Accessor
42 import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters
43 import com.intellij.util.xmlb.XmlSerializer
44 import com.intellij.util.xmlb.annotations.OptionTag
45 import org.jdom.Element
46 import org.jetbrains.annotations.TestOnly
47 import org.jetbrains.concurrency.Promise
48 import org.jetbrains.concurrency.resolvedPromise
49 import org.jetbrains.concurrency.runAsync
51 import java.util.function.Function
53 const val PROFILE = "profile"
55 private const val VERSION = "1.0"
56 private const val SCOPE = "scope"
57 private const val NAME = "name"
58 private const val PROJECT_DEFAULT_PROFILE_NAME = "Project Default"
60 private val defaultSchemeDigest = loadElement("""<component name="InspectionProjectProfileManager">
61 <profile version="1.0">
62 <option name="myName" value="Project Default" />
64 </component>""").digest()
66 @State(name = "InspectionProjectProfileManager", storages = arrayOf(Storage(value = "inspectionProfiles/profiles_settings.xml", exclusive = true)))
67 class ProjectInspectionProfileManager(val project: Project,
68 private val applicationProfileManager: InspectionProfileManager,
69 private val scopeManager: DependencyValidationManager,
70 private val localScopesHolder: NamedScopeManager,
71 schemeManagerFactory: SchemeManagerFactory) : BaseInspectionProfileManager(project.messageBus), PersistentStateComponent<Element> {
74 fun getInstanceImpl(project: Project): ProjectInspectionProfileManager {
75 return InspectionProjectProfileManager.getInstance(project) as ProjectInspectionProfileManager
79 private var scopeListener: NamedScopesHolder.ScopeListener? = null
81 private var state = State()
83 private val initialLoadSchemesFuture: Promise<Any?>
85 private val skipDefaultsSerializationFilter = object : SkipDefaultValuesSerializationFilters(State()) {
86 override fun accepts(accessor: Accessor, bean: Any, beanValue: Any?): Boolean {
87 if (beanValue == null && accessor.name == "projectProfile") {
90 return super.accepts(accessor, bean, beanValue)
94 private val schemeManagerIprProvider = if (project.isDirectoryBased) null else SchemeManagerIprProvider("profile")
96 override val schemeManager = schemeManagerFactory.create("inspectionProfiles", object : InspectionProfileProcessor() {
97 override fun createScheme(dataHolder: SchemeDataHolder<InspectionProfileImpl>,
99 attributeProvider: Function<String, String?>,
100 isBundled: Boolean): InspectionProfileImpl {
101 val profile = InspectionProfileImpl(name, InspectionToolRegistrar.getInstance(), this@ProjectInspectionProfileManager,
102 InspectionProfileImpl.getDefaultProfile(), dataHolder)
103 profile.isProjectLevel = true
107 override fun isSchemeFile(name: CharSequence) = !StringUtil.equals(name, "profiles_settings.xml")
109 override fun isSchemeDefault(scheme: InspectionProfileImpl, digest: ByteArray): Boolean {
110 return scheme.name == PROJECT_DEFAULT_PROFILE_NAME && Arrays.equals(digest, defaultSchemeDigest)
113 override fun onSchemeDeleted(scheme: InspectionProfileImpl) {
114 schemeRemoved(scheme)
117 override fun onSchemeAdded(scheme: InspectionProfileImpl) {
118 if (scheme.wasInitialized()) {
119 fireProfileChanged(scheme)
123 override fun onCurrentSchemeSwitched(oldScheme: InspectionProfileImpl?, newScheme: InspectionProfileImpl?) {
124 for (adapter in profileListeners) {
125 adapter.profileActivated(oldScheme, newScheme)
128 }, isUseOldFileNameSanitize = true, streamProvider = schemeManagerIprProvider)
130 private data class State(@field:OptionTag("PROJECT_PROFILE") var projectProfile: String? = PROJECT_DEFAULT_PROFILE_NAME,
131 @field:OptionTag("USE_PROJECT_PROFILE") var useProjectProfile: Boolean = true)
134 val app = ApplicationManager.getApplication()
135 if (!project.isDirectoryBased || app.isUnitTestMode) {
136 initialLoadSchemesFuture = resolvedPromise()
139 initialLoadSchemesFuture = runAsync { schemeManager.loadSchemes() }
142 project.messageBus.connect().subscribe(ProjectManager.TOPIC, object: ProjectManagerListener {
143 override fun projectClosed(project: Project) {
144 val cleanupInspectionProfilesRunnable = {
145 cleanupSchemes(project)
146 (InspectionProfileManager.getInstance() as BaseInspectionProfileManager).cleanupSchemes(project)
147 fireProfilesShutdown()
150 if (app.isUnitTestMode || app.isHeadlessEnvironment) {
151 cleanupInspectionProfilesRunnable.invoke()
154 app.executeOnPooledThread(cleanupInspectionProfilesRunnable)
161 fun forceLoadSchemes() {
162 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode)
163 schemeManager.loadSchemes()
166 fun isCurrentProfileInitialized() = currentProfile.wasInitialized()
168 @Synchronized override fun updateProfile(profile: Profile) {
169 super.updateProfile(profile)
171 (profile as InspectionProfileImpl).initInspectionTools(project)
174 override fun schemeRemoved(scheme: InspectionProfile) {
175 scheme.cleanup(project)
179 private class ProjectInspectionProfileStartUpActivity : StartupActivity {
180 override fun runActivity(project: Project) {
181 getInstanceImpl(project).apply {
182 initialLoadSchemesFuture.done {
183 currentProfile.initInspectionTools(project)
184 fireProfilesInitialized()
186 val app = ApplicationManager.getApplication()
187 if (app.isUnitTestMode && app.isDispatchThread) {
188 // do not restart daemon in the middle of the test
189 //noinspection TestOnlyProblems
190 UIUtil.dispatchAllInvocationEvents()
194 scopeListener = NamedScopesHolder.ScopeListener {
195 for (profile in schemeManager.allSchemes) {
196 profile.scopesChanged()
200 scopeManager.addScopeListener(scopeListener!!)
201 localScopesHolder.addScopeListener(scopeListener!!)
202 Disposer.register(project, Disposable {
203 scopeManager.removeScopeListener(scopeListener!!)
204 localScopesHolder.removeScopeListener(scopeListener!!)
210 @Synchronized override fun getState(): Element? {
211 val result = Element("settings")
213 schemeManagerIprProvider?.writeState(result)
215 val state = this.state
216 if (state.useProjectProfile) {
217 state.projectProfile = schemeManager.currentSchemeName
220 XmlSerializer.serializeInto(state, result, skipDefaultsSerializationFilter)
221 if (!result.children.isEmpty()) {
222 result.addContent(Element("version").setAttribute("value", VERSION))
225 severityRegistrar.writeExternal(result)
227 return wrapState(result, project)
230 @Synchronized override fun loadState(state: Element) {
231 val data = unwrapState(state, project, schemeManagerIprProvider, schemeManager)
233 val newState = State()
237 severityRegistrar.readExternal(it)
239 catch (e: Throwable) {
243 XmlSerializer.deserializeInto(newState, it)
246 this.state = newState
248 if (data != null && data.getChild("version")?.getAttributeValue("value") != VERSION) {
249 for (o in data.getChildren("option")) {
250 if (o.getAttributeValue("name") == "USE_PROJECT_LEVEL_SETTINGS") {
251 if (o.getAttributeValue("value").toBoolean()) {
252 if (newState.projectProfile != null) {
253 currentProfile.convert(data, project)
261 if (newState.useProjectProfile) {
262 schemeManager.currentSchemeName = newState.projectProfile
266 override fun getScopesManager() = scopeManager
268 @Synchronized override fun getProfiles(): Collection<Profile> {
270 return schemeManager.allSchemes
273 @Synchronized override fun getAvailableProfileNames(): Array<String> = schemeManager.allSchemeNames.toTypedArray()
275 val projectProfile: String?
276 get() = schemeManager.currentSchemeName
278 @Synchronized override fun setRootProfile(name: String?) {
279 if (name != schemeManager.currentSchemeName) {
280 schemeManager.currentSchemeName = name
281 state.useProjectProfile = name != null
285 @Synchronized fun useApplicationProfile(name: String) {
286 schemeManager.currentSchemeName = null
287 state.useProjectProfile = false
288 // yes, we reuse the same field - useProjectProfile field will be used to distinguish — is it app or project level
289 // to avoid data format change
290 state.projectProfile = name
293 @Synchronized fun setCurrentProfile(profile: InspectionProfileImpl?) {
294 schemeManager.setCurrent(profile)
295 state.useProjectProfile = profile != null
298 @Synchronized override fun getCurrentProfile(): InspectionProfileImpl {
299 if (!state.useProjectProfile) {
300 return (state.projectProfile?.let {
301 applicationProfileManager.getProfile(it, false)
302 } ?: applicationProfileManager.currentProfile) as InspectionProfileImpl
305 var currentScheme = schemeManager.currentScheme
306 if (currentScheme == null) {
307 currentScheme = schemeManager.allSchemes.firstOrNull()
308 if (currentScheme == null) {
309 currentScheme = InspectionProfileImpl(PROJECT_DEFAULT_PROFILE_NAME, InspectionToolRegistrar.getInstance(), this,
310 InspectionProfileImpl.getDefaultProfile(), null)
311 currentScheme.copyFrom(applicationProfileManager.currentProfile)
312 currentScheme.isProjectLevel = true
313 currentScheme.name = PROJECT_DEFAULT_PROFILE_NAME
314 schemeManager.addScheme(currentScheme)
316 schemeManager.setCurrent(currentScheme, false)
321 private fun fireProfilesInitialized() {
322 for (listener in profileListeners) {
323 listener.profilesInitialized()
327 private fun fireProfilesShutdown() {
328 for (profileChangeAdapter in profileListeners) {
329 profileChangeAdapter.profilesShutdown()
333 @Synchronized override fun getProfile(name: String, returnRootProfileIfNamedIsAbsent: Boolean): Profile? {
334 val profile = schemeManager.findSchemeByName(name)
335 return profile ?: applicationProfileManager.getProfile(name, returnRootProfileIfNamedIsAbsent)