auto-coder 0.1.327__py3-none-any.whl → 0.1.329__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

@@ -0,0 +1,680 @@
1
+ """
2
+ Module for compiling Java code.
3
+ This module provides functionality to compile Java code and report compilation errors.
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import tempfile
9
+ import time
10
+ import re
11
+ from typing import Dict, List, Any, Optional, Tuple
12
+
13
+ from autocoder.compilers.base_compiler import BaseCompiler
14
+ from autocoder.compilers.models import (
15
+ CompilationError,
16
+ FileCompilationResult,
17
+ ProjectCompilationResult,
18
+ CompilationErrorPosition,
19
+ CompilationErrorSeverity
20
+ )
21
+
22
+ class JavaCompiler(BaseCompiler):
23
+ """
24
+ A class that provides compilation functionality for Java code.
25
+ """
26
+
27
+ def __init__(self, verbose: bool = False):
28
+ """
29
+ Initialize the JavaCompiler.
30
+
31
+ Args:
32
+ verbose (bool): Whether to display verbose output.
33
+ """
34
+ super().__init__(verbose)
35
+
36
+ def get_supported_extensions(self) -> List[str]:
37
+ """
38
+ Get the list of file extensions supported by this compiler.
39
+
40
+ Returns:
41
+ List[str]: List of supported file extensions.
42
+ """
43
+ return ['.java']
44
+
45
+ def _check_dependencies(self) -> bool:
46
+ """
47
+ Check if required dependencies (javac) are installed.
48
+
49
+ Returns:
50
+ bool: True if all dependencies are available, False otherwise.
51
+ """
52
+ try:
53
+ # Check if javac is installed
54
+ process = subprocess.run(
55
+ ['javac', '-version'],
56
+ stdout=subprocess.PIPE,
57
+ stderr=subprocess.PIPE,
58
+ text=True
59
+ )
60
+ return process.returncode == 0
61
+ except (subprocess.SubprocessError, FileNotFoundError):
62
+ return False
63
+
64
+ def _parse_javac_error(self, error_line: str, file_path: Optional[str] = None) -> Optional[CompilationError]:
65
+ """
66
+ Parse a javac error message into a CompilationError object.
67
+
68
+ Args:
69
+ error_line (str): Error line from javac output
70
+ file_path (Optional[str]): The file path to use if not found in error message
71
+
72
+ Returns:
73
+ Optional[CompilationError]: Parsed error or None if couldn't parse
74
+ """
75
+ # Common javac error format: file_path:line: error: message
76
+ match = re.match(r'(.*?):(\d+)(?::(\d+))?: (error|warning): (.*)', error_line)
77
+ if match:
78
+ error_file = match.group(1)
79
+ line = int(match.group(2))
80
+ column = int(match.group(3)) if match.group(3) else None
81
+ severity_str = match.group(4)
82
+ message = match.group(5)
83
+
84
+ # Use provided file_path if error_file is not absolute
85
+ if file_path and not os.path.isabs(error_file):
86
+ # Try to find the full path
87
+ if os.path.exists(os.path.join(os.path.dirname(file_path), error_file)):
88
+ error_file = os.path.join(os.path.dirname(file_path), error_file)
89
+ else:
90
+ error_file = file_path
91
+
92
+ return CompilationError(
93
+ message=message,
94
+ severity=CompilationErrorSeverity.ERROR if severity_str == 'error' else CompilationErrorSeverity.WARNING,
95
+ position=CompilationErrorPosition(
96
+ line=line,
97
+ column=column
98
+ ),
99
+ file_path=error_file,
100
+ code="java-compilation-error"
101
+ )
102
+
103
+ return None
104
+
105
+ def compile_file(self, file_path: str) -> Dict[str, Any]:
106
+ """
107
+ Compile a single Java file.
108
+
109
+ Args:
110
+ file_path (str): Path to the file to compile.
111
+
112
+ Returns:
113
+ Dict[str, Any]: Compilation results.
114
+ """
115
+ if not os.path.exists(file_path):
116
+ return FileCompilationResult(
117
+ file_path=file_path,
118
+ success=False,
119
+ language="java",
120
+ error_message=f"File not found: {file_path}"
121
+ ).model_dump()
122
+
123
+ if not self.is_supported_file(file_path):
124
+ return FileCompilationResult(
125
+ file_path=file_path,
126
+ success=False,
127
+ language="java",
128
+ error_message=f"Unsupported file type: {file_path}"
129
+ ).model_dump()
130
+
131
+ if not self._check_dependencies():
132
+ return FileCompilationResult(
133
+ file_path=file_path,
134
+ success=False,
135
+ language="java",
136
+ error_message="Java compiler (javac) not found. Please make sure JDK is installed."
137
+ ).model_dump()
138
+
139
+ start_time = time.time()
140
+ errors = []
141
+ error_count = 0
142
+ warning_count = 0
143
+
144
+ try:
145
+ # Create a temporary directory for output
146
+ with tempfile.TemporaryDirectory() as temp_dir:
147
+ # Run javac on the file
148
+ cmd = ['javac', '-d', temp_dir, file_path]
149
+
150
+ process = subprocess.run(
151
+ cmd,
152
+ stdout=subprocess.PIPE,
153
+ stderr=subprocess.PIPE,
154
+ text=True
155
+ )
156
+
157
+ # Parse compilation errors
158
+ if process.returncode != 0:
159
+ success = False
160
+ # Parse error output
161
+ error_lines = process.stderr.splitlines()
162
+
163
+ for line in error_lines:
164
+ if not line.strip():
165
+ continue
166
+
167
+ error = self._parse_javac_error(line, file_path)
168
+ if error:
169
+ errors.append(error)
170
+ if error.severity == CompilationErrorSeverity.ERROR:
171
+ error_count += 1
172
+ elif error.severity == CompilationErrorSeverity.WARNING:
173
+ warning_count += 1
174
+ else:
175
+ success = True
176
+
177
+ # Get output file location (class file)
178
+ output_file = None
179
+ class_name = os.path.splitext(os.path.basename(file_path))[0]
180
+ class_file = os.path.join(temp_dir, f"{class_name}.class")
181
+
182
+ if os.path.exists(class_file):
183
+ output_file = class_file
184
+
185
+ except Exception as e:
186
+ return FileCompilationResult(
187
+ file_path=file_path,
188
+ success=False,
189
+ language="java",
190
+ error_message=f"Error during compilation: {str(e)}"
191
+ ).model_dump()
192
+
193
+ # Calculate execution time
194
+ execution_time_ms = int((time.time() - start_time) * 1000)
195
+
196
+ # Create the result
197
+ result = FileCompilationResult(
198
+ file_path=file_path,
199
+ success=success,
200
+ language="java",
201
+ errors=errors,
202
+ error_count=error_count,
203
+ warning_count=warning_count,
204
+ info_count=0,
205
+ execution_time_ms=execution_time_ms,
206
+ output_file=output_file
207
+ )
208
+
209
+ return result.model_dump()
210
+
211
+ def _is_maven_project(self, project_path: str) -> bool:
212
+ """
213
+ Check if a directory is a Maven project.
214
+
215
+ Args:
216
+ project_path (str): Path to the project directory.
217
+
218
+ Returns:
219
+ bool: True if it's a Maven project (has pom.xml), False otherwise.
220
+ """
221
+ return os.path.exists(os.path.join(project_path, 'pom.xml'))
222
+
223
+ def _is_gradle_project(self, project_path: str) -> bool:
224
+ """
225
+ Check if a directory is a Gradle project.
226
+
227
+ Args:
228
+ project_path (str): Path to the project directory.
229
+
230
+ Returns:
231
+ bool: True if it's a Gradle project (has build.gradle), False otherwise.
232
+ """
233
+ return (os.path.exists(os.path.join(project_path, 'build.gradle')) or
234
+ os.path.exists(os.path.join(project_path, 'build.gradle.kts')))
235
+
236
+ def _compile_maven_project(self, project_path: str) -> Dict[str, Any]:
237
+ """
238
+ Compile a Maven project.
239
+
240
+ Args:
241
+ project_path (str): Path to the Maven project directory.
242
+
243
+ Returns:
244
+ Dict[str, Any]: Compilation results.
245
+ """
246
+ errors = []
247
+ error_count = 0
248
+ warning_count = 0
249
+ success = False
250
+
251
+ try:
252
+ # Run Maven compile
253
+ cmd = ['mvn', 'compile', '-B']
254
+
255
+ process = subprocess.run(
256
+ cmd,
257
+ cwd=project_path,
258
+ stdout=subprocess.PIPE,
259
+ stderr=subprocess.PIPE,
260
+ text=True
261
+ )
262
+
263
+ success = process.returncode == 0
264
+
265
+ # Parse Maven output for errors
266
+ if not success:
267
+ output = process.stdout + process.stderr
268
+ error_pattern = r'\[ERROR\] (.*?):(\d+)(?::(\d+))?: (.*)'
269
+
270
+ for line in output.splitlines():
271
+ match = re.search(error_pattern, line)
272
+ if match:
273
+ source_file = match.group(1)
274
+ line_number = int(match.group(2))
275
+ column = int(match.group(3)) if match.group(3) else None
276
+ message = match.group(4)
277
+
278
+ error = CompilationError(
279
+ message=message,
280
+ severity=CompilationErrorSeverity.ERROR,
281
+ position=CompilationErrorPosition(
282
+ line=line_number,
283
+ column=column
284
+ ),
285
+ file_path=source_file,
286
+ code="maven-compile-error"
287
+ )
288
+
289
+ errors.append(error)
290
+ error_count += 1
291
+
292
+ # Check for warnings too
293
+ warning_pattern = r'\[WARNING\] (.*?):(\d+)(?::(\d+))?: (.*)'
294
+ output = process.stdout + process.stderr
295
+
296
+ for line in output.splitlines():
297
+ match = re.search(warning_pattern, line)
298
+ if match:
299
+ source_file = match.group(1)
300
+ line_number = int(match.group(2))
301
+ column = int(match.group(3)) if match.group(3) else None
302
+ message = match.group(4)
303
+
304
+ warning = CompilationError(
305
+ message=message,
306
+ severity=CompilationErrorSeverity.WARNING,
307
+ position=CompilationErrorPosition(
308
+ line=line_number,
309
+ column=column
310
+ ),
311
+ file_path=source_file,
312
+ code="maven-compile-warning"
313
+ )
314
+
315
+ errors.append(warning)
316
+ warning_count += 1
317
+
318
+ return {
319
+ 'success': success,
320
+ 'errors': errors,
321
+ 'error_count': error_count,
322
+ 'warning_count': warning_count,
323
+ 'output_directory': os.path.join(project_path, 'target', 'classes') if success else None
324
+ }
325
+
326
+ except Exception as e:
327
+ return {
328
+ 'success': False,
329
+ 'errors': [],
330
+ 'error_count': 0,
331
+ 'warning_count': 0,
332
+ 'error_message': f"Error during Maven compilation: {str(e)}"
333
+ }
334
+
335
+ def _compile_gradle_project(self, project_path: str) -> Dict[str, Any]:
336
+ """
337
+ Compile a Gradle project.
338
+
339
+ Args:
340
+ project_path (str): Path to the Gradle project directory.
341
+
342
+ Returns:
343
+ Dict[str, Any]: Compilation results.
344
+ """
345
+ errors = []
346
+ error_count = 0
347
+ warning_count = 0
348
+ success = False
349
+
350
+ try:
351
+ # Run Gradle build
352
+ cmd = ['./gradlew', 'compileJava', '--console=plain']
353
+ if not os.path.exists(os.path.join(project_path, 'gradlew')):
354
+ cmd = ['gradle', 'compileJava', '--console=plain']
355
+
356
+ process = subprocess.run(
357
+ cmd,
358
+ cwd=project_path,
359
+ stdout=subprocess.PIPE,
360
+ stderr=subprocess.PIPE,
361
+ text=True
362
+ )
363
+
364
+ success = process.returncode == 0
365
+
366
+ # Parse Gradle output for errors
367
+ if not success:
368
+ output = process.stdout + process.stderr
369
+ # Gradle error pattern: file_path:line: error: message
370
+ error_pattern = r'(.*?):(\d+)(?::(\d+))?: (error|warning): (.*)'
371
+
372
+ for line in output.splitlines():
373
+ match = re.search(error_pattern, line)
374
+ if match:
375
+ source_file = match.group(1)
376
+ line_number = int(match.group(2))
377
+ column = int(match.group(3)) if match.group(3) else None
378
+ severity_type = match.group(4)
379
+ message = match.group(5)
380
+
381
+ error = CompilationError(
382
+ message=message,
383
+ severity=CompilationErrorSeverity.ERROR if severity_type == 'error' else CompilationErrorSeverity.WARNING,
384
+ position=CompilationErrorPosition(
385
+ line=line_number,
386
+ column=column
387
+ ),
388
+ file_path=source_file,
389
+ code="gradle-compile-error"
390
+ )
391
+
392
+ errors.append(error)
393
+ if severity_type == 'error':
394
+ error_count += 1
395
+ else:
396
+ warning_count += 1
397
+
398
+ return {
399
+ 'success': success,
400
+ 'errors': errors,
401
+ 'error_count': error_count,
402
+ 'warning_count': warning_count,
403
+ 'output_directory': os.path.join(project_path, 'build', 'classes') if success else None
404
+ }
405
+
406
+ except Exception as e:
407
+ return {
408
+ 'success': False,
409
+ 'errors': [],
410
+ 'error_count': 0,
411
+ 'warning_count': 0,
412
+ 'error_message': f"Error during Gradle compilation: {str(e)}"
413
+ }
414
+
415
+ def _compile_standard_java_project(self, project_path: str) -> Dict[str, Any]:
416
+ """
417
+ Compile a standard Java project (not using Maven or Gradle).
418
+
419
+ Args:
420
+ project_path (str): Path to the Java project directory.
421
+
422
+ Returns:
423
+ Dict[str, Any]: Compilation results.
424
+ """
425
+ errors = []
426
+ error_count = 0
427
+ warning_count = 0
428
+ success = True
429
+ file_results = {}
430
+
431
+ try:
432
+ # Create a temporary directory for output
433
+ with tempfile.TemporaryDirectory() as temp_dir:
434
+ # Find all Java files
435
+ java_files = []
436
+ for root, _, files in os.walk(project_path):
437
+ for file in files:
438
+ if file.endswith(".java"):
439
+ java_files.append(os.path.join(root, file))
440
+
441
+ if not java_files:
442
+ return {
443
+ 'success': True,
444
+ 'errors': [],
445
+ 'error_count': 0,
446
+ 'warning_count': 0,
447
+ 'file_results': {},
448
+ 'message': "No Java files found in the project"
449
+ }
450
+
451
+ # First try to compile all files together
452
+ all_files_cmd = ['javac', '-d', temp_dir] + java_files
453
+
454
+ process = subprocess.run(
455
+ all_files_cmd,
456
+ stdout=subprocess.PIPE,
457
+ stderr=subprocess.PIPE,
458
+ text=True
459
+ )
460
+
461
+ if process.returncode == 0:
462
+ # Successfully compiled all files together
463
+ return {
464
+ 'success': True,
465
+ 'errors': [],
466
+ 'error_count': 0,
467
+ 'warning_count': 0,
468
+ 'file_results': {f: {'success': True, 'error_count': 0} for f in java_files},
469
+ 'output_directory': temp_dir
470
+ }
471
+
472
+ # If all-at-once compilation failed, try compiling each file individually
473
+ for file_path in java_files:
474
+ file_result = self.compile_file(file_path)
475
+ file_results[file_path] = file_result
476
+
477
+ if not file_result['success']:
478
+ success = False
479
+
480
+ error_count += file_result['error_count']
481
+ warning_count += file_result['warning_count']
482
+ errors.extend(file_result['errors'])
483
+
484
+ return {
485
+ 'success': success,
486
+ 'errors': errors,
487
+ 'error_count': error_count,
488
+ 'warning_count': warning_count,
489
+ 'file_results': file_results,
490
+ 'output_directory': temp_dir if success else None
491
+ }
492
+
493
+ except Exception as e:
494
+ return {
495
+ 'success': False,
496
+ 'errors': [],
497
+ 'error_count': 0,
498
+ 'warning_count': 0,
499
+ 'error_message': f"Error during Java compilation: {str(e)}"
500
+ }
501
+
502
+ def compile_project(self, project_path: str) -> Dict[str, Any]:
503
+ """
504
+ Compile a Java project.
505
+
506
+ Args:
507
+ project_path (str): Path to the project directory.
508
+
509
+ Returns:
510
+ Dict[str, Any]: Compilation results.
511
+ """
512
+ if not os.path.exists(project_path):
513
+ return ProjectCompilationResult(
514
+ project_path=project_path,
515
+ success=False,
516
+ total_files=0,
517
+ error_message=f"Project directory not found: {project_path}"
518
+ ).model_dump()
519
+
520
+ if not self._check_dependencies():
521
+ return ProjectCompilationResult(
522
+ project_path=project_path,
523
+ success=False,
524
+ total_files=0,
525
+ error_message="Java compiler (javac) not found. Please make sure JDK is installed."
526
+ ).model_dump()
527
+
528
+ start_time = time.time()
529
+
530
+ # Determine project type and compile accordingly
531
+ if self._is_maven_project(project_path):
532
+ maven_result = self._compile_maven_project(project_path)
533
+ success = maven_result['success']
534
+ errors = maven_result.get('errors', [])
535
+ error_count = maven_result.get('error_count', 0)
536
+ warning_count = maven_result.get('warning_count', 0)
537
+ output_directory = maven_result.get('output_directory')
538
+ error_message = maven_result.get('error_message')
539
+
540
+ # Count Java files in src/main/java
541
+ java_dir = os.path.join(project_path, 'src', 'main', 'java')
542
+ total_files = 0
543
+ file_results = {}
544
+
545
+ if os.path.exists(java_dir):
546
+ for root, _, files in os.walk(java_dir):
547
+ for file in files:
548
+ if file.endswith(".java"):
549
+ java_file = os.path.join(root, file)
550
+ total_files += 1
551
+
552
+ # Create a file result for each Java file
553
+ file_errors = [e for e in errors if e.file_path == java_file]
554
+ file_results[java_file] = FileCompilationResult(
555
+ file_path=java_file,
556
+ success=len(file_errors) == 0,
557
+ language="java",
558
+ errors=file_errors,
559
+ error_count=len([e for e in file_errors if e.severity == CompilationErrorSeverity.ERROR]),
560
+ warning_count=len([e for e in file_errors if e.severity == CompilationErrorSeverity.WARNING]),
561
+ info_count=0
562
+ ).model_dump()
563
+
564
+ elif self._is_gradle_project(project_path):
565
+ gradle_result = self._compile_gradle_project(project_path)
566
+ success = gradle_result['success']
567
+ errors = gradle_result.get('errors', [])
568
+ error_count = gradle_result.get('error_count', 0)
569
+ warning_count = gradle_result.get('warning_count', 0)
570
+ output_directory = gradle_result.get('output_directory')
571
+ error_message = gradle_result.get('error_message')
572
+
573
+ # Count Java files in src/main/java
574
+ java_dir = os.path.join(project_path, 'src', 'main', 'java')
575
+ total_files = 0
576
+ file_results = {}
577
+
578
+ if os.path.exists(java_dir):
579
+ for root, _, files in os.walk(java_dir):
580
+ for file in files:
581
+ if file.endswith(".java"):
582
+ java_file = os.path.join(root, file)
583
+ total_files += 1
584
+
585
+ # Create a file result for each Java file
586
+ file_errors = [e for e in errors if e.file_path == java_file]
587
+ file_results[java_file] = FileCompilationResult(
588
+ file_path=java_file,
589
+ success=len(file_errors) == 0,
590
+ language="java",
591
+ errors=file_errors,
592
+ error_count=len([e for e in file_errors if e.severity == CompilationErrorSeverity.ERROR]),
593
+ warning_count=len([e for e in file_errors if e.severity == CompilationErrorSeverity.WARNING]),
594
+ info_count=0
595
+ ).model_dump()
596
+
597
+ else:
598
+ # Standard Java project
599
+ standard_result = self._compile_standard_java_project(project_path)
600
+ success = standard_result['success']
601
+ errors = standard_result.get('errors', [])
602
+ error_count = standard_result.get('error_count', 0)
603
+ warning_count = standard_result.get('warning_count', 0)
604
+ output_directory = standard_result.get('output_directory')
605
+ error_message = standard_result.get('error_message')
606
+ file_results = standard_result.get('file_results', {})
607
+
608
+ # Count total files
609
+ total_files = 0
610
+ for root, _, files in os.walk(project_path):
611
+ for file in files:
612
+ if file.endswith(".java"):
613
+ total_files += 1
614
+
615
+ # Calculate execution time
616
+ execution_time_ms = int((time.time() - start_time) * 1000)
617
+
618
+ # Count files with errors
619
+ files_with_errors = len([f for f in file_results.values() if f.get('error_count', 0) > 0])
620
+
621
+ # Create the project result
622
+ result = ProjectCompilationResult(
623
+ project_path=project_path,
624
+ success=success,
625
+ total_files=total_files,
626
+ files_with_errors=files_with_errors,
627
+ total_errors=error_count,
628
+ total_warnings=warning_count,
629
+ total_infos=0,
630
+ file_results={p: FileCompilationResult(**r) for p, r in file_results.items()},
631
+ error_message=error_message,
632
+ output_directory=output_directory
633
+ )
634
+
635
+ return result.model_dump()
636
+
637
+ def format_compile_result(self, compile_result: Dict[str, Any]) -> str:
638
+ """
639
+ Format compilation results into a human-readable string.
640
+
641
+ Args:
642
+ compile_result (Dict[str, Any]): The compilation result dictionary.
643
+
644
+ Returns:
645
+ str: A formatted string representation of the compilation results.
646
+ """
647
+ if 'project_path' in compile_result:
648
+ # This is a project result
649
+ return ProjectCompilationResult(**compile_result).to_str()
650
+ else:
651
+ # This is a file result
652
+ return FileCompilationResult(**compile_result).to_str()
653
+
654
+ def compile_java_file(file_path: str, verbose: bool = False) -> Dict[str, Any]:
655
+ """
656
+ Utility function to compile a single Java file.
657
+
658
+ Args:
659
+ file_path (str): Path to the file to compile.
660
+ verbose (bool): Whether to display verbose output.
661
+
662
+ Returns:
663
+ Dict[str, Any]: A dictionary containing compilation results.
664
+ """
665
+ compiler = JavaCompiler(verbose=verbose)
666
+ return compiler.compile_file(file_path)
667
+
668
+ def compile_java_project(project_path: str, verbose: bool = False) -> Dict[str, Any]:
669
+ """
670
+ Utility function to compile a Java project.
671
+
672
+ Args:
673
+ project_path (str): Path to the project directory.
674
+ verbose (bool): Whether to display verbose output.
675
+
676
+ Returns:
677
+ Dict[str, Any]: A dictionary containing compilation results.
678
+ """
679
+ compiler = JavaCompiler(verbose=verbose)
680
+ return compiler.compile_project(project_path)