1515package java
1616
1717import (
18+ "archive/tar"
19+ "compress/gzip"
1820 "fmt"
21+ "io"
22+ "log"
23+ "net/http"
1924 "os"
2025 "path/filepath"
2126 "runtime"
@@ -26,25 +31,188 @@ import (
2631 "github.com/cloudwego/abcoder/lang/utils"
2732)
2833
29- const MaxWaitDuration = 5 * time .Second
34+ const (
35+ MaxWaitDuration = 5 * time .Second
36+ jdtlsVersion = "1.39.0-202408291433"
37+ jdtlsURL = "https://download.eclipse.org/jdtls/milestones/1.39.0/jdt-language-server-1.39.0-202408291433.tar.gz"
38+ )
39+
40+ // untar takes a destination path and a reader; a tar reader loops over the tar file
41+ // and writes each file to the destination path.
42+ func untar (dst string , r io.Reader ) error {
43+ gzr , err := gzip .NewReader (r )
44+ if err != nil {
45+ return err
46+ }
47+ defer gzr .Close ()
48+
49+ tr := tar .NewReader (gzr )
50+
51+ for {
52+ header , err := tr .Next ()
53+
54+ switch {
55+ // if no more files are found return
56+ case err == io .EOF :
57+ return nil
58+ // return any other error
59+ case err != nil :
60+ return err
61+ // if the header is nil, just skip it (not sure how this happens)
62+ case header == nil :
63+ continue
64+ }
65+
66+ // the target location where the dir/file should be created
67+ target := filepath .Join (dst , header .Name )
68+
69+ // check the file type
70+ switch header .Typeflag {
71+
72+ // if its a dir and it doesn't exist create it
73+ case tar .TypeDir :
74+ if _ , err := os .Stat (target ); err != nil {
75+ if err := os .MkdirAll (target , 0755 ); err != nil {
76+ return err
77+ }
78+ }
79+
80+ // if it's a file create it
81+ case tar .TypeReg :
82+ // make sure the directory for the file exists
83+ if err := os .MkdirAll (filepath .Dir (target ), 0755 ); err != nil {
84+ return err
85+ }
86+
87+ f , err := os .OpenFile (target , os .O_CREATE | os .O_RDWR , os .FileMode (header .Mode ))
88+ if err != nil {
89+ return err
90+ }
91+
92+ // copy over contents
93+ if _ , err := io .Copy (f , tr ); err != nil {
94+ f .Close ()
95+ return err
96+ }
97+
98+ // manually close here after each file operation; defering would cause each file close
99+ // to wait until all operations have completed.
100+ f .Close ()
101+ }
102+ }
103+ }
104+
105+ func setupJDTLS () (string , error ) {
106+ _ , currentFile , _ , ok := runtime .Caller (0 )
107+ if ! ok {
108+ return "" , fmt .Errorf ("failed to get current file path" )
109+ }
110+ javaDir := filepath .Dir (currentFile )
111+ installDir := filepath .Join (javaDir , "lsp" , "jdtls" )
112+
113+ // Check for any existing JDTLS installation
114+ existingDirs , err := filepath .Glob (filepath .Join (installDir , "jdt-language-server-*" ))
115+ if err == nil && len (existingDirs ) > 0 {
116+ for _ , dir := range existingDirs {
117+ info , err := os .Stat (dir )
118+ if err == nil && info .IsDir () {
119+ // Check if launcher jar exists in this directory
120+ launcherPattern := filepath .Join (dir , "plugins" , "org.eclipse.equinox.launcher_*.jar" )
121+ matches , err := filepath .Glob (launcherPattern )
122+ if err == nil && len (matches ) > 0 {
123+ log .Printf ("Found existing JDT Language Server at %s. Skipping installation." , dir )
124+ return dir , nil
125+ }
126+ }
127+ }
128+ }
129+
130+ log .Printf ("JDT Language Server not found locally. Downloading and installing version %s..." , jdtlsVersion )
131+ jdtlsDir := filepath .Join (installDir , "jdt-language-server-" + jdtlsVersion )
132+
133+ // Create download directory
134+ downloadDir := filepath .Join (installDir , "download" )
135+ if err := os .MkdirAll (downloadDir , 0755 ); err != nil {
136+ return "" , fmt .Errorf ("failed to create download directory: %w" , err )
137+ }
138+
139+ // Download
140+ tarballName := "jdt-language-server-" + jdtlsVersion + ".tar.gz"
141+ tarballPath := filepath .Join (downloadDir , tarballName )
142+ log .Printf ("Downloading from %s..." , jdtlsURL )
143+ resp , err := http .Get (jdtlsURL )
144+ if err != nil {
145+ return "" , fmt .Errorf ("failed to download JDTLS: %w" , err )
146+ }
147+ defer resp .Body .Close ()
148+
149+ if resp .StatusCode != http .StatusOK {
150+ return "" , fmt .Errorf ("failed to download JDTLS: received status code %d" , resp .StatusCode )
151+ }
152+
153+ out , err := os .Create (tarballPath )
154+ if err != nil {
155+ return "" , fmt .Errorf ("failed to create tarball file: %w" , err )
156+ }
157+ //defer os.Remove(tarballPath) // Clean up tarball after function returns
158+
159+ _ , err = io .Copy (out , resp .Body )
160+ if err != nil {
161+ out .Close ()
162+ return "" , fmt .Errorf ("failed to save tarball: %w" , err )
163+ }
164+ out .Close () // Close file before untarring
165+
166+ // Extract
167+ log .Printf ("Extracting to %s..." , installDir )
168+ file , err := os .Open (tarballPath )
169+ if err != nil {
170+ return "" , fmt .Errorf ("failed to open tarball: %w" , err )
171+ }
172+ defer file .Close ()
173+
174+ if err := untar (jdtlsDir , file ); err != nil {
175+ return "" , fmt .Errorf ("failed to extract JDTLS: %w" , err )
176+ }
177+
178+ log .Printf ("JDT Language Server installed successfully in %s." , jdtlsDir )
179+ return jdtlsDir , nil
180+ }
30181
31182func GetDefaultLSP (LspOptions map [string ]string ) (lang uniast.Language , name string ) {
32183 return uniast .Java , generateExecuteCmd (LspOptions )
33184}
34185
35186func generateExecuteCmd (LspOptions map [string ]string ) string {
187+ var jdtRootPATH string
188+ // First, check environment variable
189+ if envPath := os .Getenv ("JDTLS_ROOT_PATH" ); len (envPath ) != 0 {
190+ jdtRootPATH = envPath
191+ log .Printf ("Using JDTLS_ROOT_PATH from environment: %s" , jdtRootPATH )
192+ } else {
193+ // If env var is not set, run auto-setup
194+ var err error
195+ jdtRootPATH , err = setupJDTLS ()
196+ if err != nil {
197+ panic (fmt .Sprintf ("Failed to setup JDT Language Server: %v" , err ))
198+ }
199+ }
200+
36201 // Get the absolute path to the current file
37202 _ , currentFile , _ , ok := runtime .Caller (0 )
38203 if ! ok {
39204 panic ("Failed to get current file path" )
40205 }
41206 javaDir := filepath .Dir (currentFile )
42207
43- jdtRootPATH := filepath .Join (javaDir , "lsp" , "jdtls" , "jdt-language-server-1.39.0-202408291433" )
44- if len (os .Getenv ("JDTlS_ROOT_PATH" )) != 0 {
45- jdtRootPATH = os .Getenv ("JDTlS_ROOT_PATH" )
208+ // Find launcher jar
209+ launcherPattern := filepath .Join (jdtRootPATH , "plugins" , "org.eclipse.equinox.launcher_*.jar" )
210+ matches , err := filepath .Glob (launcherPattern )
211+ if err != nil || len (matches ) == 0 {
212+ panic (fmt .Sprintf ("Could not find org.eclipse.equinox.launcher_*.jar in %s/plugins" , jdtRootPATH ))
46213 }
47- jdtLsPath := filepath .Join (jdtRootPATH , "plugins" , "org.eclipse.equinox.launcher_1.6.900.v20240613-2009.jar" )
214+ jdtLsPath := matches [0 ]
215+
48216 // Determine the configuration path based on OS and architecture
49217 var osName string
50218 switch runtime .GOOS {
0 commit comments