import { lstat } from "fs-extra" ; import * as globby from "globby" ; import * as _ from "lodash" ; import { platform } from "os" ; import { dirname , extname , isAbsolute , join , relative } from "path" ; import { CustomExecution , Event , EventEmitter , Pseudoterminal , Task , TaskDefinition , TaskProvider , TaskRevealKind , tasks , TerminalDimensions , Uri , workspace , WorkspaceFolder , } from "vscode" ; import { buildWorkspace } from "../build" ; import { Jdtls } from "../java/jdtls" ; import { INodeData } from "../java/nodeData" ; import { languageServerApiManager } from "../languageServerApi/languageServerApiManager" ; import { Settings } from "../settings" ; import { IUriData , Trie , TrieNode } from "../views/nodeCache/Trie" ; import { IClasspathResult } from "./GenerateJarExecutor" ; import { IExportJarStepExecutor } from "./IExportJarStepExecutor" ; import { IClasspath , IStepMetadata } from "./IStepMetadata" ; import { IMainClassInfo } from "./ResolveMainClassExecutor" ; import { ExportJarConstants , ExportJarMessages , ExportJarStep , failMessage , getExtensionApi , resetStepMetadata , revealTerminal , stepMap , successMessage , toPosixPath , toWinPath , } from "./utility" ; interface IExportJarTaskDefinition extends TaskDefinition { label ? : string ; mainClass ? : string ; targetPath ? : string ; elements ? : string [ ] ; } let isExportingJar = false ; const activeTerminalMap = new Map < string , ExportJarTaskTerminal > ( ) ; export async function executeExportJarTask ( node ? ) { await workspace . saveAll ( false ) ; if ( ! await languageServerApiManager . ready ( ) || isExportingJar || await buildWorkspace ( ) === false ) { return ; } isExportingJar = true ; const stepMetadata = { entry : node , taskLabel : "exportjar:default" , steps : [ ] , projectList : [ ] , elements : [ ] , classpaths : [ ] , } ; try { const resolveJavaProjectExecutor = stepMap . get ( ExportJarStep . ResolveJavaProject ) ; if ( ! resolveJavaProjectExecutor ) { throw new Error ( ExportJarMessages . stepErrorMessage ( ExportJarMessages . StepAction . FINDEXECUTOR , ExportJarStep . ResolveJavaProject ) ) ; } await resolveJavaProjectExecutor . execute ( stepMetadata ) ; tasks . executeTask ( ExportJarTaskProvider . getDefaultTask ( stepMetadata ) ) ; } catch ( err ) { if ( err ) { failMessage ( `${ err }` ) ; } isExportingJar = false ; return ; } } export class ExportJarTaskProvider implements TaskProvider { public static exportJarType : string = "java" ; public static getDefaultTask ( stepMetadata ) { if ( ! stepMetadata . workspaceFolder ) { throw new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . WORKSPACEFOLDER , ExportJarStep . ResolveTask ) ) ; } const defaultDefinition = { type : ExportJarTaskProvider . exportJarType , label : "exportjar:default" , targetPath : Settings . getExportJarTargetPath ( ) , elements : [ ] , mainClass : undefined , } ; const task = new Task ( defaultDefinition , stepMetadata . workspaceFolder , "exportjar:default" , ExportJarTaskProvider . exportJarType , new CustomExecution ( async ( resolvedDefinition ) : Promise < Pseudoterminal > => { return new ExportJarTaskTerminal ( resolvedDefinition , stepMetadata ) ; } ) ) ; task . presentationOptions . reveal = TaskRevealKind . Never ; return task ; } private tasks : Task [ ] | undefined ; public async resolveTask ( task ) { const definition = < IExportJarTaskDefinition > task . definition ; const folder = < WorkspaceFolder > task . scope ; const resolvedTask = new Task ( definition , folder , task . name , ExportJarTaskProvider . exportJarType , new CustomExecution ( async ( resolvedDefinition ) : Promise < Pseudoterminal > => { const stepMetadata = { entry : undefined , taskLabel : resolvedDefinition . label || `exportjar:${ folder . name }` , workspaceFolder : folder , projectList : await Jdtls . getProjects ( folder . uri . toString ( ) ) , steps : [ ] , elements : [ ] , classpaths : [ ] , } ; return new ExportJarTaskTerminal ( resolvedDefinition , stepMetadata ) ; } ) ) ; resolvedTask . presentationOptions . reveal = TaskRevealKind . Never ; return resolvedTask ; } public async provideTasks ( ) { const folders = workspace . workspaceFolders || [ ] ; if ( _ . isEmpty ( folders ) ) { return undefined ; } if ( ! _ . isEmpty ( this . tasks ) ) { return this . tasks ; } this . tasks = [ ] ; for ( const folder of folders ) { const projectList = await Jdtls . getProjects ( folder . uri . toString ( ) ) ; const elementList = [ ] ; if ( _ . isEmpty ( projectList ) ) { continue ; } else if ( projectList . length === 1 ) { elementList . push ( "${" + ExportJarConstants . COMPILE_OUTPUT + "}" , "${" + ExportJarConstants . DEPENDENCIES + "}" ) ; } else { for ( const project of projectList ) { elementList . push ( "${" + ExportJarConstants . COMPILE_OUTPUT + ":" + project . name + "}" , "${" + ExportJarConstants . DEPENDENCIES + ":" + project . name + "}" ) ; } } const mainClasses = await Jdtls . getMainClasses ( folder . uri . toString ( ) ) ; const defaultDefinition = { type : ExportJarTaskProvider . exportJarType , mainClass : ( mainClasses . length === 1 ) ? mainClasses [ 0 ] . name : undefined , targetPath : Settings . getExportJarTargetPath ( ) , elements : elementList , } ; const defaultTask = new Task ( defaultDefinition , folder , `exportjar:${ folder . name }` , ExportJarTaskProvider . exportJarType , new CustomExecution ( async ( resolvedDefinition ) : Promise < Pseudoterminal > => { const stepMetadata = { entry : undefined , taskLabel : resolvedDefinition . label || `exportjar:${ folder . name }` , workspaceFolder : folder , projectList : await Jdtls . getProjects ( folder . uri . toString ( ) ) , steps : [ ] , elements : [ ] , classpaths : [ ] , } ; return new ExportJarTaskTerminal ( resolvedDefinition , stepMetadata ) ; } ) , undefined ) ; defaultTask . presentationOptions . reveal = TaskRevealKind . Never ; this . tasks . push ( defaultTask ) ; } return this . tasks ; } } class ExportJarTaskTerminal implements Pseudoterminal { public writeEmitter = new EventEmitter < string > ( ) ; public closeEmitter = new EventEmitter < number > ( ) ; public onDidWrite : Event < string > = this . writeEmitter . event ; public onDidClose ? : Event < number > = this . closeEmitter . event ; public terminalId : string ; private stepMetadata : IStepMetadata ; constructor ( exportJarTaskDefinition , stepMetadata ) { this . stepMetadata = stepMetadata ; this . stepMetadata . taskLabel = exportJarTaskDefinition . label || "" ; this . stepMetadata . terminalId = Math . floor ( Math . random ( ) * Number . MAX_SAFE_INTEGER ) . toString ( ) ; this . stepMetadata . mainClass = exportJarTaskDefinition . mainClass ; this . stepMetadata . outputPath = exportJarTaskDefinition . targetPath ; this . stepMetadata . elements = exportJarTaskDefinition . elements || [ ] ; this . terminalId = this . stepMetadata . terminalId ; } public exit ( message ? ) { if ( message ) { this . writeEmitter . fire ( message ) ; } if ( activeTerminalMap . has ( this . terminalId ) ) { activeTerminalMap . delete ( this . terminalId ) ; this . closeEmitter . fire ( 0 ) ; } } public async open ( _initialDimensions ) { activeTerminalMap . set ( this . terminalId , this ) ; revealTerminal ( this . stepMetadata . taskLabel ) ; let exportResult ; try { if ( ! this . stepMetadata . workspaceFolder ) { throw new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . WORKSPACEFOLDER , ExportJarStep . ResolveTask ) ) ; } if ( this . stepMetadata . outputPath === undefined ) { this . stepMetadata . outputPath = "" ; } if ( ! _ . isEmpty ( this . stepMetadata . elements ) ) { const outputFolderMap = new Map < string , string [ ] > ( ) ; const artifactMap = new Map < string , string [ ] > ( ) ; const testOutputFolderMap = new Map < string , string [ ] > ( ) ; const testArtifactMap = new Map < string , string [ ] > ( ) ; const projectList = await Jdtls . getProjects ( this . stepMetadata . workspaceFolder . uri . toString ( ) ) ; for ( const project of projectList ) { await this . setClasspathMap ( project , "runtime" , outputFolderMap , artifactMap ) ; await this . setClasspathMap ( project , "test" , testOutputFolderMap , testArtifactMap ) ; } this . stepMetadata . classpaths = await this . resolveClasspaths ( outputFolderMap , artifactMap , testOutputFolderMap , testArtifactMap ) ; } exportResult = await this . createJarFile ( this . stepMetadata ) ; } catch ( err ) { if ( err ) { failMessage ( `${ err }` ) ; this . exit ( "[ERROR] An error occurs during export Jar process" ) ; } else { this . exit ( "[CANCEL] Export Jar process is cancelled by user" ) ; } } finally { isExportingJar = false ; if ( exportResult === true ) { successMessage ( this . stepMetadata . outputPath ) ; this . exit ( "[SUCCESS] Export Jar process is finished successfully" ) ; } else if ( exportResult === false ) { executeExportJarTask ( this . stepMetadata . entry ) ; } this . exit ( ) ; } } public close ( ) { } private async createJarFile ( stepMetadata ) { let step = ExportJarStep . ResolveJavaProject ; let previousStep ; let executor ; while ( step !== ExportJarStep . Finish ) { executor = stepMap . get ( step ) ; if ( ! executor ) { throw new Error ( ExportJarMessages . stepErrorMessage ( ExportJarMessages . StepAction . FINDEXECUTOR , step ) ) ; } if ( ! await executor . execute ( stepMetadata ) ) { previousStep = stepMetadata . steps . pop ( ) ; if ( ! previousStep ) { throw new Error ( ExportJarMessages . stepErrorMessage ( ExportJarMessages . StepAction . GOBACK , step ) ) ; } resetStepMetadata ( previousStep , stepMetadata ) ; step = previousStep ; } else { switch ( step ) { case ExportJarStep . ResolveJavaProject : step = ExportJarStep . ResolveMainClass ; break ; case ExportJarStep . ResolveMainClass : step = ExportJarStep . GenerateJar ; break ; case ExportJarStep . GenerateJar : step = ExportJarStep . Finish ; break ; default : throw new Error ( ExportJarMessages . stepErrorMessage ( ExportJarMessages . StepAction . GOAHEAD , step ) ) ; } } if ( step === ExportJarStep . ResolveJavaProject ) { return false ; } } return true ; } private async setClasspathMap ( project , classpathScope , outputFolderMap , artifactMap ) { const extensionApi = await getExtensionApi ( ) ; const classpathResult = await extensionApi . getClasspaths ( project . uri , { scope : classpathScope } ) ; const outputFolders = [ ] ; const artifacts = [ ] ; for ( const classpath of [ ... classpathResult . classpaths , ... classpathResult . modulepaths ] ) { if ( extname ( classpath ) === ".jar" ) { artifacts . push ( classpath ) ; } else { outputFolders . push ( classpath ) ; } } outputFolderMap . set ( project . name , outputFolders ) ; artifactMap . set ( project . name , artifacts ) ; } private async resolveClasspaths ( outputFolderMap , artifactMap , testOutputFolderMap , testArtifactMap ) { const regExp = /\${(.*?)(:.*)?}/ ; let outputElements = [ ] ; let artifacts = [ ] ; for ( const element of this . stepMetadata . elements ) { if ( element . length === 0 ) { continue ; } const matchResult = element . match ( regExp ) ; if ( matchResult === null || _ . isEmpty ( matchResult ) || matchResult . length <= 2 ) { if ( extname ( element ) === ".jar" ) { artifacts . push ( this . toAbsolutePosixPath ( element ) ) ; } else { outputElements . push ( this . toAbsolutePosixPath ( element ) ) ; } continue ; } const projectName = matchResult [ 2 ] ?. substring ( 1 ) ; switch ( matchResult [ 1 ] ) { case ExportJarConstants . DEPENDENCIES : artifacts = artifacts . concat ( this . getJarElementsFromClasspathMapping ( matchResult , artifactMap , projectName ) ) ; break ; case ExportJarConstants . TEST_DEPENDENCIES : artifacts = artifacts . concat ( this . getJarElementsFromClasspathMapping ( matchResult , testArtifactMap , projectName ) ) ; break ; case ExportJarConstants . COMPILE_OUTPUT : outputElements = outputElements . concat ( this . getJarElementsFromClasspathMapping ( matchResult , outputFolderMap , projectName ) ) ; break ; case ExportJarConstants . TEST_COMPILE_OUTPUT : outputElements = outputElements . concat ( this . getJarElementsFromClasspathMapping ( matchResult , testOutputFolderMap , projectName ) ) ; break ; } } const trie = new Trie < IUriData > ( ) ; const globPatterns = [ ] ; for ( const outputElement of outputElements ) { if ( outputElement . length === 0 ) { continue ; } if ( outputElement [ 0 ] !== "!" ) { const uri = Uri . file ( platform ( ) === "win32" ? toWinPath ( outputElement ) : outputElement ) ; const uriData = { uri : uri . toString ( ) , } ; trie . insert ( uriData ) ; } globPatterns . push ( outputElement ) ; } const sources = [ ] ; for ( const glob of await globby ( globPatterns ) ) { const tireNode = trie . find ( Uri . file ( platform ( ) === "win32" ? toWinPath ( glob ) : glob ) . fsPath , true ) ; if ( ! tireNode ?. value ?. uri ) { continue ; } let fsPath = Uri . parse ( tireNode . value . uri ) . fsPath ; if ( ( await lstat ( fsPath ) ) . isFile ( ) ) { fsPath = dirname ( fsPath ) ; } if ( ! _ . isEmpty ( tireNode ) ) { const classpath = { source : glob , destination : relative ( fsPath , glob ) , isArtifact : false , } ; sources . push ( classpath ) ; } } for ( const artifact of await globby ( artifacts ) ) { const classpath = { source : artifact , destination : undefined , isArtifact : true , } ; sources . push ( classpath ) ; } return sources ; } private getJarElementsFromClasspathMapping ( matchResult , rawClasspathEntries , projectName ) { const result = [ ] ; if ( ! matchResult . input ) { return result ; } if ( projectName !== undefined ) { const entries = rawClasspathEntries . get ( projectName ) || [ ] ; if ( _ . isEmpty ( entries ) ) { return result ; } for ( const classpath of entries ) { result . push ( this . toAbsolutePosixPath ( matchResult . input . replace ( matchResult [ 0 ] , classpath ) ) ) ; } } else { for ( const classpaths of rawClasspathEntries . values ( ) ) { for ( const classpath of classpaths ) { result . push ( this . toAbsolutePosixPath ( matchResult . input . replace ( matchResult [ 0 ] , classpath ) ) ) ; } } } return result ; } private toAbsolutePosixPath ( path ) { if ( ! this . stepMetadata . workspaceFolder ) { throw new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . WORKSPACEFOLDER , ExportJarStep . ResolveTask ) ) ; } const negative = ( path [ 0 ] === "!" ) ; let positivePath = negative ? path . substring ( 1 ) : path ; if ( ! isAbsolute ( positivePath ) ) { positivePath = join ( this . stepMetadata . workspaceFolder . uri . fsPath , positivePath ) ; } positivePath = toPosixPath ( positivePath ) ; return negative ? "!" + positivePath : positivePath ; } } export function appendOutput ( terminalId , message ) { const terminal = activeTerminalMap . get ( terminalId ) ; if ( ! terminal ) { return ; } terminal . writeEmitter . fire ( message + "\r\n" ) ; }