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 org.jetbrains.idea.maven.buildtool
4 import com.intellij.build.*
5 import com.intellij.build.events.EventResult
6 import com.intellij.build.events.MessageEvent
7 import com.intellij.build.events.MessageEventResult
8 import com.intellij.build.events.impl.*
9 import com.intellij.build.issue.BuildIssue
10 import com.intellij.build.issue.BuildIssueQuickFix
11 import com.intellij.build.process.BuildProcessHandler
12 import com.intellij.icons.AllIcons
13 import com.intellij.openapi.actionSystem.AnAction
14 import com.intellij.openapi.actionSystem.AnActionEvent
15 import com.intellij.openapi.components.ServiceManager
16 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
17 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
18 import com.intellij.openapi.progress.EmptyProgressIndicator
19 import com.intellij.openapi.progress.ProgressIndicator
20 import com.intellij.openapi.project.Project
21 import com.intellij.openapi.util.registry.Registry
22 import com.intellij.openapi.util.text.StringUtil
23 import com.intellij.openapi.vfs.VirtualFile
24 import com.intellij.pom.Navigatable
25 import com.intellij.util.ExceptionUtil
26 import org.jetbrains.annotations.ApiStatus
27 import org.jetbrains.annotations.Nls
28 import org.jetbrains.idea.maven.buildtool.quickfix.OffMavenOfflineModeQuickFix
29 import org.jetbrains.idea.maven.buildtool.quickfix.OpenMavenSettingsQuickFix
30 import org.jetbrains.idea.maven.buildtool.quickfix.UseBundledMavenQuickFix
31 import org.jetbrains.idea.maven.execution.SyncBundle
32 import org.jetbrains.idea.maven.project.MavenProjectsManager
33 import org.jetbrains.idea.maven.project.MavenWorkspaceSettingsComponent
34 import org.jetbrains.idea.maven.server.MavenServerManager
35 import org.jetbrains.idea.maven.server.MavenServerProgressIndicator
36 import org.jetbrains.idea.maven.utils.MavenLog
37 import org.jetbrains.idea.maven.utils.MavenUtil
39 import java.io.OutputStream
40 import javax.swing.JComponent
42 class MavenSyncConsole(private val myProject: Project) {
44 private var mySyncView: BuildProgressListener = BuildProgressListener { _, _ -> }
45 private var mySyncId = ExternalSystemTaskId.create(MavenUtil.SYSTEM_ID, ExternalSystemTaskType.RESOLVE_PROJECT, myProject)
46 private var finished = false
47 private var started = false
48 private var wrapperProgressIndicator: WrapperProgressIndicator? = null
49 private var hasErrors = false
50 private var hasUnresolved = false
51 private val JAVADOC_AND_SOURCE_CLASSIFIERS = setOf("javadoc", "sources", "test-javadoc", "test-sources")
52 private val delayedActions = ArrayList<() -> Unit>()
54 private var myStartedSet = LinkedHashSet<Pair<Any, String>>()
57 fun startImport(syncView: BuildProgressListener) {
61 val restartAction: AnAction = object : AnAction() {
62 override fun update(e: AnActionEvent) {
63 e.presentation.isEnabled = !started || finished
64 e.presentation.icon = AllIcons.Actions.Refresh
67 override fun actionPerformed(e: AnActionEvent) {
69 MavenProjectsManager.getInstance(it).forceUpdateAllProjectsOrFindAllAvailablePomFiles()
77 wrapperProgressIndicator = WrapperProgressIndicator()
79 mySyncId = ExternalSystemTaskId.create(MavenUtil.SYSTEM_ID, ExternalSystemTaskType.RESOLVE_PROJECT, myProject)
80 val runDescr = BuildContentDescriptor(null, null, object : JComponent() {}, SyncBundle.message("maven.sync.title"))
81 runDescr.isActivateToolWindowWhenFailed = true
82 runDescr.isActivateToolWindowWhenAdded = false
84 val descriptor = DefaultBuildDescriptor(mySyncId, SyncBundle.message("maven.sync.title"), myProject.basePath!!,
85 System.currentTimeMillis())
86 .withRestartAction(restartAction)
87 .withContentDescriptor{runDescr}
89 mySyncView.onEvent(mySyncId, StartBuildEventImpl(descriptor, SyncBundle.message("maven.sync.project.title", myProject.name)))
90 debugLog("maven sync: started importing $myProject")
94 fun addText(text: String) = doIfImportInProcess {
95 addText(mySyncId, text, true)
99 private fun addText(parentId: Any, text: String, stdout: Boolean) = doIfImportInProcess {
100 if (StringUtil.isEmpty(text)) {
103 val toPrint = if (text.endsWith('\n')) text else "$text\n"
104 mySyncView.onEvent(mySyncId, OutputBuildEventImpl(parentId, toPrint, stdout))
108 fun addWarning(@Nls text: String, @Nls description: String) = doIfImportInProcess {
109 mySyncView.onEvent(mySyncId,
110 MessageEventImpl(mySyncId, MessageEvent.Kind.WARNING, SyncBundle.message("maven.sync.group.compiler"), text,
116 debugLog("Maven sync: finishImport")
122 fun terminated(exitCode: Int) = doIfImportInProcess {
123 val tasks = myStartedSet.toList().asReversed()
124 debugLog("Tasks $tasks are not completed! Force complete")
125 tasks.forEach { completeTask(it.first, it.second, FailureResultImpl(SyncBundle.message("maven.sync.failure.terminated", exitCode))) }
127 mySyncView.onEvent(mySyncId, FinishBuildEventImpl(mySyncId, null, System.currentTimeMillis(), "",
128 FailureResultImpl(SyncBundle.message("maven.sync.failure.terminated", exitCode))))
135 fun startWrapperResolving() {
136 if (!started || finished) {
137 startImport(ServiceManager.getService(myProject, SyncViewManager::class.java))
139 startTask(mySyncId, SyncBundle.message("maven.sync.wrapper"))
143 fun finishWrapperResolving(e: Throwable? = null) {
145 addWarning(SyncBundle.message("maven.sync.wrapper.failure"), e.localizedMessage)
147 completeTask(mySyncId, SyncBundle.message("maven.sync.wrapper"), SuccessResultImpl())
150 fun progressIndicatorForWrapper(): ProgressIndicator {
151 return wrapperProgressIndicator ?: EmptyProgressIndicator()
154 inner class WrapperProgressIndicator : EmptyProgressIndicator() {
155 var myFraction: Long = 0
156 override fun setText(text: String) = doIfImportInProcess {
157 addText(SyncBundle.message("maven.sync.wrapper"), text, true)
160 override fun setFraction(fraction: Double) = doIfImportInProcess {
161 val newFraction = (fraction * 100).toLong()
162 if (myFraction == newFraction) return@doIfImportInProcess
164 mySyncView.onEvent(mySyncId,
165 ProgressBuildEventImpl(SyncBundle.message("maven.sync.wrapper"), SyncBundle.message("maven.sync.wrapper"),
166 System.currentTimeMillis(),
167 SyncBundle.message("maven.sync.wrapper.dowloading"),
169 (fraction * 100).toLong(),
176 fun notifyReadingProblems(file: VirtualFile) = doIfImportInProcess {
177 debugLog("reading problems in $file")
179 val desc = SyncBundle.message("maven.sync.failure.error.reading.file", file.path)
180 mySyncView.onEvent(mySyncId,
181 FileMessageEventImpl(mySyncId, MessageEvent.Kind.ERROR, SyncBundle.message("maven.sync.group.error"), desc, desc,
182 FilePosition(File(file.path), -1, -1)))
187 fun addException(e: Throwable, progressListener: BuildProgressListener) {
188 if(started && !finished){
191 mySyncView.onEvent(mySyncId,
192 MessageEventImpl(mySyncId, MessageEvent.Kind.ERROR, "Error", e.localizedMessage, ExceptionUtil.getThrowableText(e)))
194 this.startImport(progressListener)
195 this.addException(e, progressListener)
200 fun getListener(type: MavenServerProgressIndicator.ResolveType): ArtifactSyncListener {
202 MavenServerProgressIndicator.ResolveType.PLUGIN -> ArtifactSyncListenerImpl("maven.sync.plugins")
203 MavenServerProgressIndicator.ResolveType.DEPENDENCY -> ArtifactSyncListenerImpl("maven.sync.dependencies")
208 private fun doFinish() {
209 val tasks = myStartedSet.toList().asReversed()
210 debugLog("Tasks $tasks are not completed! Force complete")
211 tasks.forEach { completeTask(it.first, it.second, DerivedResultImpl()) }
212 mySyncView.onEvent(mySyncId, FinishBuildEventImpl(mySyncId, null, System.currentTimeMillis(), "",
213 if (hasErrors) FailureResultImpl() else DerivedResultImpl()))
214 val generalSettings = MavenWorkspaceSettingsComponent.getInstance(myProject).settings.generalSettings
215 if (hasUnresolved && generalSettings.isWorkOffline) {
216 mySyncView.onEvent(mySyncId, BuildIssueEventImpl(mySyncId, object : BuildIssue {
217 override val title: String = "Dependency Resolution Failed"
218 override val description: String = "<a href=\"${OffMavenOfflineModeQuickFix.ID}\">Switch Off Offline Mode</a>\n"
219 override val quickFixes: List<BuildIssueQuickFix> = listOf(OffMavenOfflineModeQuickFix())
221 override fun getNavigatable(project: Project): Navigatable? = null
222 }, MessageEvent.Kind.ERROR))
229 private fun showError(keyPrefix: String, dependency: String) = doIfImportInProcess {
232 val umbrellaString = SyncBundle.message("${keyPrefix}.resolve")
233 val errorString = SyncBundle.message("${keyPrefix}.resolve.error", dependency)
234 startTask(mySyncId, umbrellaString)
235 mySyncView.onEvent(mySyncId, MessageEventImpl(umbrellaString, MessageEvent.Kind.ERROR, "Error", errorString, errorString))
236 addText(mySyncId, errorString, false)
240 private fun startTask(parentId: Any, taskName: String) = doIfImportInProcess {
241 debugLog("Maven sync: start $taskName")
242 if (myStartedSet.add(parentId to taskName)) {
243 mySyncView.onEvent(mySyncId, StartEventImpl(taskName, parentId, System.currentTimeMillis(), taskName))
249 private fun completeTask(parentId: Any, taskName: String, result: EventResult) = doIfImportInProcess {
250 hasErrors = hasErrors || result is FailureResultImpl
252 debugLog("Maven sync: complete $taskName with $result")
253 if (myStartedSet.remove(parentId to taskName)) {
254 mySyncView.onEvent(mySyncId, FinishEventImpl(taskName, parentId, System.currentTimeMillis(), taskName, result))
259 private fun debugLog(s: String, exception: Throwable? = null) {
260 MavenLog.LOG.debug(s, exception)
264 private fun completeUmbrellaEvents(keyPrefix: String) = doIfImportInProcess {
265 val taskName = SyncBundle.message("${keyPrefix}.resolve")
266 completeTask(mySyncId, taskName, DerivedResultImpl())
270 private fun downloadEventStarted(keyPrefix: String, dependency: String) = doIfImportInProcess {
271 val downloadString = SyncBundle.message("${keyPrefix}.download")
272 val downloadArtifactString = SyncBundle.message("${keyPrefix}.artifact.download", dependency)
273 startTask(mySyncId, downloadString)
274 startTask(downloadString, downloadArtifactString)
278 private fun downloadEventCompleted(keyPrefix: String, dependency: String) = doIfImportInProcess {
279 val downloadString = SyncBundle.message("${keyPrefix}.download")
280 val downloadArtifactString = SyncBundle.message("${keyPrefix}.artifact.download", dependency)
281 addText(downloadArtifactString, downloadArtifactString, true)
282 completeTask(downloadString, downloadArtifactString, SuccessResultImpl(false))
287 private fun downloadEventFailed(keyPrefix: String, dependency: String, error: String, stackTrace: String?) = doIfImportInProcess {
288 val downloadString = SyncBundle.message("${keyPrefix}.download")
290 val downloadArtifactString = SyncBundle.message("${keyPrefix}.artifact.download", dependency)
291 if (isJavadocOrSource(dependency)) {
292 addText(downloadArtifactString, "$dependency not found", true)
293 completeTask(downloadString, downloadArtifactString, object : MessageEventResult {
294 override fun getKind(): MessageEvent.Kind {
295 return MessageEvent.Kind.WARNING
298 override fun getDetails(): String? {
299 return SyncBundle.message("maven.sync.failure.dependency.not.found", dependency)
305 if (stackTrace != null && Registry.`is`("maven.spy.events.debug")) {
306 addText(downloadArtifactString, stackTrace, false)
309 addText(downloadArtifactString, error, true)
311 completeTask(downloadString, downloadArtifactString, FailureResultImpl(error))
316 fun showQuickFixBadMaven(message: String, kind: MessageEvent.Kind) {
317 val bundledVersion = MavenServerManager.getInstance().getMavenVersion(MavenServerManager.BUNDLED_MAVEN_3)
318 mySyncView.onEvent(mySyncId, BuildIssueEventImpl(mySyncId, object : BuildIssue {
319 override val title = SyncBundle.message("maven.sync.version.issue.title")
320 override val description: String = "${message}\n" +
321 "- <a href=\"${OpenMavenSettingsQuickFix.ID}\">" +
322 SyncBundle.message("maven.sync.version.open.settings") + "</a>\n" +
323 "- <a href=\"${UseBundledMavenQuickFix.ID}\">" +
324 SyncBundle.message("maven.sync.version.use.bundled", bundledVersion) + "</a>\n"
326 override val quickFixes: List<BuildIssueQuickFix> = listOf(OpenMavenSettingsQuickFix(), UseBundledMavenQuickFix())
327 override fun getNavigatable(project: Project): Navigatable? = null
332 fun showQuickFixJDK(version: String) {
333 mySyncView.onEvent(mySyncId, BuildIssueEventImpl(mySyncId, object : BuildIssue {
334 override val title = SyncBundle.message("maven.sync.quickfixes.maven.jdk.version.title")
335 override val description: String = SyncBundle.message("maven.sync.quickfixes.upgrade.to.jdk7", version) + "\n" +
336 "- <a href=\"${OpenMavenSettingsQuickFix.ID}\">" +
337 SyncBundle.message("maven.sync.quickfixes.open.settings") +
339 override val quickFixes: List<BuildIssueQuickFix> = listOf(OpenMavenSettingsQuickFix())
340 override fun getNavigatable(project: Project): Navigatable? = null
341 }, MessageEvent.Kind.ERROR))
344 private fun isJavadocOrSource(dependency: String): Boolean {
345 val split = dependency.split(':')
346 if (split.size < 4) {
349 val classifier = split.get(2)
350 return JAVADOC_AND_SOURCE_CLASSIFIERS.contains(classifier)
353 private inline fun doIfImportInProcess(action: () -> Unit) {
354 if (!started || finished) return
359 private inner class ArtifactSyncListenerImpl(val keyPrefix: String) : ArtifactSyncListener {
360 override fun downloadStarted(dependency: String) {
361 downloadEventStarted(keyPrefix, dependency)
364 override fun downloadCompleted(dependency: String) {
365 downloadEventCompleted(keyPrefix, dependency)
368 override fun downloadFailed(dependency: String, error: String, stackTrace: String?) {
369 downloadEventFailed(keyPrefix, dependency, error, stackTrace)
372 override fun finish() {
373 completeUmbrellaEvents(keyPrefix)
376 override fun showError(dependency: String) {
377 showError(keyPrefix, dependency)
383 interface ArtifactSyncListener {
384 fun showError(dependency: String)
385 fun downloadStarted(dependency: String)
386 fun downloadCompleted(dependency: String)
387 fun downloadFailed(dependency: String, error: String, stackTrace: String?)