import { ensureDir , pathExists } from "fs-extra" ; import globby = require ( "globby" ) ; import * as _ from "lodash" ; import { basename , dirname , extname , isAbsolute , join , normalize , relative } from "path" ; import { Disposable , ProgressLocation , QuickInputButtons , QuickPickItem , Uri , window , WorkspaceFolder } from "vscode" ; import { sendInfo } from "vscode-extension-telemetry-wrapper" ; import { Jdtls } from "../java/jdtls" ; import { INodeData } from "../java/nodeData" ; import { IExportJarStepExecutor } from "./IExportJarStepExecutor" ; import { IClasspath , IStepMetadata } from "./IStepMetadata" ; import { createPickBox , ExportJarMessages , ExportJarStep , ExportJarTargets , getExtensionApi , toPosixPath } from "./utility" ; export class GenerateJarExecutor implements IExportJarStepExecutor { private readonly currentStep : ExportJarStep = ExportJarStep . GenerateJar ; public async execute ( stepMetadata ) { return this . generateJar ( stepMetadata ) ; } private async generateJar ( stepMetadata ) { if ( _ . isEmpty ( stepMetadata . elements ) ) { if ( ! ( await this . generateClasspaths ( stepMetadata ) ) ) { return false ; } } const folder = stepMetadata . workspaceFolder ; if ( ! folder ) { throw new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . WORKSPACEFOLDER , this . currentStep ) ) ; } let destPath = "" ; if ( stepMetadata . outputPath === ExportJarTargets . SETTING_ASKUSER || stepMetadata . outputPath === "" ) { if ( stepMetadata . outputPath === ExportJarTargets . SETTING_ASKUSER ) { sendInfo ( "" , { exportJarPath : stepMetadata . outputPath } ) ; } const outputUri = await window . showSaveDialog ( { defaultUri : Uri . file ( join ( folder . uri . fsPath , `${ folder . name }.jar` ) ) , filters : { "Java Archive" : [ "jar" ] , } , } ) ; if ( ! outputUri ) { return Promise . reject ( ) ; } destPath = outputUri . fsPath ; } else { const outputPath = stepMetadata . outputPath ; if ( ! outputPath ) { throw new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . OUTPUTPATH , this . currentStep ) ) ; } destPath = ( isAbsolute ( outputPath ) ) ? outputPath : join ( folder . uri . fsPath , outputPath ) ; if ( extname ( outputPath ) !== ".jar" ) { destPath = join ( destPath , folder . name + ".jar" ) ; } await ensureDir ( dirname ( destPath ) ) ; } destPath = normalize ( destPath ) ; return window . withProgress ( { location : ProgressLocation . Window , title : "Exporting Jar : Generating jar..." , cancellable : true , } , ( _progress , token ) => { return new Promise < boolean > ( async ( resolve , reject ) => { token . onCancellationRequested ( ( ) => { return reject ( ) ; } ) ; const mainClass = stepMetadata . mainClass ; if ( mainClass === undefined ) { return reject ( new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . MAINCLASS , this . currentStep ) ) ) ; } const classpaths = stepMetadata . classpaths ; if ( _ . isEmpty ( classpaths ) ) { return reject ( new Error ( ExportJarMessages . CLASSPATHS_EMPTY ) ) ; } if ( ! stepMetadata . terminalId ) { return reject ( new Error ( "Can't find related terminal." ) ) ; } const exportResult = await Jdtls . exportJar ( basename ( mainClass ) , classpaths , destPath , stepMetadata . terminalId , token ) ; if ( exportResult === true ) { stepMetadata . outputPath = destPath ; return resolve ( true ) ; } else { return reject ( new Error ( "Export jar failed." ) ) ; } } ) ; } ) ; } private async generateClasspaths ( stepMetadata ) { const extensionApi = await getExtensionApi ( ) ; const dependencyItems = await window . withProgress ( { location : ProgressLocation . Window , title : "Exporting Jar : Resolving classpaths..." , cancellable : true , } , ( _progress , token ) => { return new Promise < IJarQuickPickItem [ ] > ( async ( resolve , reject ) => { token . onCancellationRequested ( ( ) => { return reject ( ) ; } ) ; const pickItems = [ ] ; const uriSet = new Set < string > ( ) ; const projectList = stepMetadata . projectList ; if ( _ . isEmpty ( projectList ) ) { return reject ( new Error ( ExportJarMessages . WORKSPACE_EMPTY ) ) ; } const workspaceFolder = stepMetadata . workspaceFolder ; if ( ! workspaceFolder ) { return reject ( new Error ( ExportJarMessages . fieldUndefinedMessage ( ExportJarMessages . Field . WORKSPACEFOLDER , this . currentStep ) ) ) ; } for ( const project of projectList ) { const projectUri = project . metaData ?. UnmanagedFolderInnerPath || project . uri ; let classpaths ; let testClasspaths ; try { classpaths = await extensionApi . getClasspaths ( projectUri , { scope : "runtime" } ) ; testClasspaths = await extensionApi . getClasspaths ( projectUri , { scope : "test" } ) ; } catch ( e ) { return reject ( new Error ( e ) ) ; } pickItems . push ( ... await this . parseDependencyItems ( classpaths . classpaths , uriSet , workspaceFolder . uri . fsPath , "runtime" ) , ... await this . parseDependencyItems ( classpaths . modulepaths , uriSet , workspaceFolder . uri . fsPath , "runtime" ) ) ; pickItems . push ( ... await this . parseDependencyItems ( testClasspaths . classpaths , uriSet , workspaceFolder . uri . fsPath , "test" ) , ... await this . parseDependencyItems ( testClasspaths . modulepaths , uriSet , workspaceFolder . uri . fsPath , "test" ) ) ; } return resolve ( pickItems ) ; } ) ; } ) ; if ( _ . isEmpty ( dependencyItems ) ) { throw new Error ( ExportJarMessages . PROJECT_EMPTY ) ; } else if ( dependencyItems . length === 1 ) { await this . setStepMetadataFromOutputFolder ( dependencyItems [ 0 ] . path , stepMetadata . classpaths ) ; return true ; } dependencyItems . sort ( ( node1 , node2 ) => { if ( node1 . description !== node2 . description ) { return ( node1 . description || "" ) . localeCompare ( node2 . description || "" ) ; } if ( node1 . type !== node2 . type ) { return node2 . type . localeCompare ( node1 . type ) ; } return node1 . label . localeCompare ( node2 . label ) ; } ) ; const pickedDependencyItems = [ ] ; for ( const item of dependencyItems ) { if ( item . picked ) { pickedDependencyItems . push ( item ) ; } } const disposables = [ ] ; let result = false ; try { result = await new Promise < boolean > ( async ( resolve , reject ) => { const pickBox = createPickBox < IJarQuickPickItem > ( "Export Jar : Determine elements" , "Select the elements" , dependencyItems , stepMetadata . steps . length > 0 , true ) ; pickBox . selectedItems = pickedDependencyItems ; disposables . push ( pickBox . onDidTriggerButton ( ( item ) => { if ( item === QuickInputButtons . Back ) { return resolve ( false ) ; } } ) , pickBox . onDidAccept ( async ( ) => { if ( _ . isEmpty ( pickBox . selectedItems ) ) { return ; } for ( const item of pickBox . selectedItems ) { if ( item . type === "artifact" ) { const classpath = { source : item . path , destination : undefined , isArtifact : true , } ; stepMetadata . classpaths . push ( classpath ) ; } else { await this . setStepMetadataFromOutputFolder ( item . path , stepMetadata . classpaths ) ; } } return resolve ( true ) ; } ) , pickBox . onDidHide ( ( ) => { return reject ( ) ; } ) ) ; disposables . push ( pickBox ) ; pickBox . show ( ) ; } ) ; } finally { for ( const d of disposables ) { d . dispose ( ) ; } } return result ; } private async setStepMetadataFromOutputFolder ( folderPath , classpaths ) { const posixPath = toPosixPath ( folderPath ) ; for ( const path of await globby ( posixPath ) ) { const classpath = { source : path , destination : relative ( posixPath , path ) , isArtifact : false , } ; classpaths . push ( classpath ) ; } } private async parseDependencyItems ( paths , uriSet , projectPath , scope ) { const dependencyItems = [ ] ; for ( const classpath of paths ) { if ( await pathExists ( classpath ) === false ) { continue ; } const extName = extname ( classpath ) ; const baseName = Uri . parse ( classpath ) . fsPath . startsWith ( Uri . parse ( projectPath ) . fsPath ) ? relative ( projectPath , classpath ) : basename ( classpath ) ; const typeValue = ( extName === ".jar" ) ? "artifact" : "outputFolder" ; if ( ! uriSet . has ( classpath ) ) { uriSet . add ( classpath ) ; dependencyItems . push ( { label : baseName , description : scope , path : classpath , type : typeValue , picked : scope === "runtime" , } ) ; } } return dependencyItems ; } } export interface IClasspathResult { projectRoot : string ; classpaths : string [ ] ; modulepaths : string [ ] ; } interface IJarQuickPickItem extends QuickPickItem { path : string ; type : string ; }