001/* 002 * Copyright (C) 2022 - 2024, the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.github.ascopes.jct.compilers; 017 018import static io.github.ascopes.jct.utils.IterableUtils.requireNonNullValues; 019import static java.util.Objects.requireNonNull; 020 021import io.github.ascopes.jct.compilers.impl.JctCompilationFactoryImpl; 022import io.github.ascopes.jct.compilers.impl.JctCompilationImpl; 023import io.github.ascopes.jct.ex.JctCompilerException; 024import io.github.ascopes.jct.filemanagers.AnnotationProcessorDiscovery; 025import io.github.ascopes.jct.filemanagers.JctFileManagerFactory; 026import io.github.ascopes.jct.filemanagers.JctFileManagers; 027import io.github.ascopes.jct.filemanagers.LoggingMode; 028import io.github.ascopes.jct.workspaces.Workspace; 029import java.io.IOException; 030import java.nio.charset.Charset; 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.List; 034import java.util.Locale; 035import java.util.Set; 036import javax.annotation.processing.Processor; 037import org.jspecify.annotations.Nullable; 038 039/** 040 * Common functionality for a compiler that can be overridden and that produces a 041 * {@link JctCompilationImpl} as the compilation result. 042 * 043 * <p>Implementations should extend this class and override anything they require. 044 * In most cases, you should not need to override anything other than the constructor. 045 * 046 * <p>This class is <strong>not thread-safe</strong>. 047 * 048 * <p>If you wish to create a common set of configuration settings for instances of 049 * this class, you should consider writing a custom {@link JctCompilerConfigurer} object to apply 050 * the desired operations, and then apply it to instances of this class using 051 * {@link #configure(JctCompilerConfigurer)}. 052 * 053 * @author Ashley Scopes 054 * @since 0.0.1 055 */ 056public abstract class AbstractJctCompiler implements JctCompiler { 057 058 private final List<Processor> annotationProcessors; 059 private final List<String> annotationProcessorOptions; 060 private final List<String> compilerOptions; 061 062 private String name; 063 private boolean showWarnings; 064 private boolean showDeprecationWarnings; 065 private boolean failOnWarnings; 066 private CompilationMode compilationMode; 067 private Locale locale; 068 private Charset logCharset; 069 private boolean verbose; 070 private boolean previewFeatures; 071 private LoggingMode diagnosticLoggingMode; 072 private boolean fixJvmModulePathMismatch; 073 private boolean inheritClassPath; 074 private boolean inheritModulePath; 075 private boolean inheritSystemModulePath; 076 private LoggingMode fileManagerLoggingMode; 077 private AnnotationProcessorDiscovery annotationProcessorDiscovery; 078 private Set<DebuggingInfo> debuggingInfo; 079 private boolean parameterInfoEnabled; 080 081 private @Nullable String release; 082 private @Nullable String source; 083 private @Nullable String target; 084 085 /** 086 * Initialize this compiler. 087 * 088 * @param defaultName the printable default name to use for the compiler. 089 */ 090 protected AbstractJctCompiler(String defaultName) { 091 name = requireNonNull(defaultName, "name"); 092 093 annotationProcessors = new ArrayList<>(); 094 annotationProcessorOptions = new ArrayList<>(); 095 compilerOptions = new ArrayList<>(); 096 showWarnings = DEFAULT_SHOW_WARNINGS; 097 showDeprecationWarnings = DEFAULT_SHOW_DEPRECATION_WARNINGS; 098 failOnWarnings = DEFAULT_FAIL_ON_WARNINGS; 099 compilationMode = DEFAULT_COMPILATION_MODE; 100 locale = DEFAULT_LOCALE; 101 logCharset = DEFAULT_LOG_CHARSET; 102 previewFeatures = DEFAULT_PREVIEW_FEATURES; 103 verbose = DEFAULT_VERBOSE; 104 diagnosticLoggingMode = DEFAULT_DIAGNOSTIC_LOGGING_MODE; 105 fixJvmModulePathMismatch = DEFAULT_FIX_JVM_MODULE_PATH_MISMATCH; 106 inheritClassPath = DEFAULT_INHERIT_CLASS_PATH; 107 inheritModulePath = DEFAULT_INHERIT_MODULE_PATH; 108 inheritSystemModulePath = DEFAULT_INHERIT_SYSTEM_MODULE_PATH; 109 fileManagerLoggingMode = DEFAULT_FILE_MANAGER_LOGGING_MODE; 110 annotationProcessorDiscovery = DEFAULT_ANNOTATION_PROCESSOR_DISCOVERY; 111 debuggingInfo = DEFAULT_DEBUGGING_INFO; 112 parameterInfoEnabled = DEFAULT_PARAMETER_INFO_ENABLED; 113 114 // If none of these are overridden then we assume the defaults instead. 115 release = null; 116 source = null; 117 target = null; 118 } 119 120 @Override 121 public JctCompilation compile(Workspace workspace) { 122 return performCompilation(workspace, null); 123 } 124 125 @Override 126 public JctCompilation compile(Workspace workspace, Collection<String> classNames) { 127 // There is no reason to invoke this overload with null values, so 128 // prevent this. 129 requireNonNullValues(classNames, "classNames"); 130 return performCompilation(workspace, classNames); 131 } 132 133 @Override 134 public final <E extends Exception> AbstractJctCompiler configure( 135 JctCompilerConfigurer<E> configurer 136 ) throws E { 137 requireNonNull(configurer, "configurer"); 138 configurer.configure(this); 139 return this; 140 } 141 142 @Override 143 public String getName() { 144 return name; 145 } 146 147 @Override 148 public AbstractJctCompiler name(String name) { 149 requireNonNull(name, "name"); 150 this.name = name; 151 return this; 152 } 153 154 @Override 155 public boolean isVerbose() { 156 return verbose; 157 } 158 159 @Override 160 public AbstractJctCompiler verbose(boolean enabled) { 161 verbose = enabled; 162 return this; 163 } 164 165 @Override 166 public boolean isPreviewFeatures() { 167 return previewFeatures; 168 } 169 170 @Override 171 public AbstractJctCompiler previewFeatures(boolean enabled) { 172 previewFeatures = enabled; 173 return this; 174 } 175 176 @Override 177 public boolean isShowWarnings() { 178 return showWarnings; 179 } 180 181 @Override 182 public AbstractJctCompiler showWarnings(boolean enabled) { 183 showWarnings = enabled; 184 return this; 185 } 186 187 @Override 188 public boolean isShowDeprecationWarnings() { 189 return showDeprecationWarnings; 190 } 191 192 @Override 193 public AbstractJctCompiler showDeprecationWarnings(boolean enabled) { 194 showDeprecationWarnings = enabled; 195 return this; 196 } 197 198 @Override 199 public boolean isFailOnWarnings() { 200 return failOnWarnings; 201 } 202 203 @Override 204 public AbstractJctCompiler failOnWarnings(boolean enabled) { 205 failOnWarnings = enabled; 206 return this; 207 } 208 209 @Override 210 public CompilationMode getCompilationMode() { 211 return compilationMode; 212 } 213 214 @Override 215 public AbstractJctCompiler compilationMode(CompilationMode compilationMode) { 216 this.compilationMode = compilationMode; 217 return this; 218 } 219 220 @Override 221 public List<String> getAnnotationProcessorOptions() { 222 return List.copyOf(annotationProcessorOptions); 223 } 224 225 @Override 226 public AbstractJctCompiler addAnnotationProcessorOptions( 227 Iterable<String> annotationProcessorOptions 228 ) { 229 requireNonNullValues(annotationProcessorOptions, "annotationProcessorOptions"); 230 annotationProcessorOptions.forEach(this.annotationProcessorOptions::add); 231 return this; 232 } 233 234 @Override 235 public List<Processor> getAnnotationProcessors() { 236 return List.copyOf(annotationProcessors); 237 } 238 239 @Override 240 public AbstractJctCompiler addAnnotationProcessors( 241 Iterable<? extends Processor> annotationProcessors 242 ) { 243 requireNonNullValues(annotationProcessors, "annotationProcessors"); 244 annotationProcessors.forEach(this.annotationProcessors::add); 245 246 return this; 247 } 248 249 @Override 250 public List<String> getCompilerOptions() { 251 return List.copyOf(compilerOptions); 252 } 253 254 @Override 255 public AbstractJctCompiler addCompilerOptions(Iterable<String> compilerOptions) { 256 requireNonNullValues(compilerOptions, "compilerOptions"); 257 compilerOptions.forEach(this.compilerOptions::add); 258 return this; 259 } 260 261 @Override 262 public String getEffectiveRelease() { 263 if (release != null) { 264 return release; 265 } 266 267 if (target != null) { 268 return target; 269 } 270 271 return getDefaultRelease(); 272 } 273 274 @Nullable 275 @Override 276 public String getRelease() { 277 return release; 278 } 279 280 @Override 281 public AbstractJctCompiler release(@Nullable String release) { 282 this.release = release; 283 284 if (release != null) { 285 source = null; 286 target = null; 287 } 288 289 return this; 290 } 291 292 @Nullable 293 @Override 294 public String getSource() { 295 return source; 296 } 297 298 @Override 299 public AbstractJctCompiler source(@Nullable String source) { 300 this.source = source; 301 if (source != null) { 302 release = null; 303 } 304 return this; 305 } 306 307 @Nullable 308 @Override 309 public String getTarget() { 310 return target; 311 } 312 313 @Override 314 public AbstractJctCompiler target(@Nullable String target) { 315 this.target = target; 316 if (target != null) { 317 release = null; 318 } 319 return this; 320 } 321 322 @Override 323 public boolean isFixJvmModulePathMismatch() { 324 return fixJvmModulePathMismatch; 325 } 326 327 @Override 328 public AbstractJctCompiler fixJvmModulePathMismatch(boolean fixJvmModulePathMismatch) { 329 this.fixJvmModulePathMismatch = fixJvmModulePathMismatch; 330 return this; 331 } 332 333 @Override 334 public boolean isInheritClassPath() { 335 return inheritClassPath; 336 } 337 338 @Override 339 public AbstractJctCompiler inheritClassPath(boolean inheritClassPath) { 340 this.inheritClassPath = inheritClassPath; 341 return this; 342 } 343 344 @Override 345 public boolean isInheritModulePath() { 346 return inheritModulePath; 347 } 348 349 @Override 350 public AbstractJctCompiler inheritModulePath(boolean inheritModulePath) { 351 this.inheritModulePath = inheritModulePath; 352 return this; 353 } 354 355 @Override 356 public boolean isInheritSystemModulePath() { 357 return inheritSystemModulePath; 358 } 359 360 @Override 361 public AbstractJctCompiler inheritSystemModulePath(boolean inheritSystemModulePath) { 362 this.inheritSystemModulePath = inheritSystemModulePath; 363 return this; 364 } 365 366 @Override 367 public Locale getLocale() { 368 return locale; 369 } 370 371 @Override 372 public AbstractJctCompiler locale(Locale locale) { 373 requireNonNull(locale, "locale"); 374 this.locale = locale; 375 return this; 376 } 377 378 @Override 379 public Charset getLogCharset() { 380 return logCharset; 381 } 382 383 @Override 384 public AbstractJctCompiler logCharset(Charset logCharset) { 385 requireNonNull(logCharset, "logCharset"); 386 this.logCharset = logCharset; 387 return this; 388 } 389 390 @Override 391 public LoggingMode getFileManagerLoggingMode() { 392 return fileManagerLoggingMode; 393 } 394 395 @Override 396 public AbstractJctCompiler fileManagerLoggingMode(LoggingMode fileManagerLoggingMode) { 397 requireNonNull(fileManagerLoggingMode, "fileManagerLoggingMode"); 398 this.fileManagerLoggingMode = fileManagerLoggingMode; 399 return this; 400 } 401 402 @Override 403 public LoggingMode getDiagnosticLoggingMode() { 404 return diagnosticLoggingMode; 405 } 406 407 @Override 408 public AbstractJctCompiler diagnosticLoggingMode(LoggingMode diagnosticLoggingMode) { 409 requireNonNull(diagnosticLoggingMode, "diagnosticLoggingMode"); 410 this.diagnosticLoggingMode = diagnosticLoggingMode; 411 return this; 412 } 413 414 @Override 415 public AnnotationProcessorDiscovery getAnnotationProcessorDiscovery() { 416 return annotationProcessorDiscovery; 417 } 418 419 @Override 420 public AbstractJctCompiler annotationProcessorDiscovery( 421 AnnotationProcessorDiscovery annotationProcessorDiscovery 422 ) { 423 requireNonNull(annotationProcessorDiscovery, 424 "annotationProcessorDiscovery"); 425 this.annotationProcessorDiscovery = annotationProcessorDiscovery; 426 return this; 427 } 428 429 @Override 430 public Set<DebuggingInfo> getDebuggingInfo() { 431 return debuggingInfo; 432 } 433 434 @Override 435 public JctCompiler debuggingInfo(Set<DebuggingInfo> debuggingInfo) { 436 requireNonNullValues(debuggingInfo, "debuggingInfo"); 437 this.debuggingInfo = Set.copyOf(debuggingInfo); 438 return this; 439 } 440 441 @Override 442 public boolean isParameterInfoEnabled() { 443 return parameterInfoEnabled; 444 } 445 446 @Override 447 public JctCompiler parameterInfoEnabled(boolean parameterInfoEnabled) { 448 this.parameterInfoEnabled = parameterInfoEnabled; 449 return this; 450 } 451 452 /** 453 * Get the string representation of the compiler. 454 * 455 * @return the string representation of the compiler. 456 */ 457 @Override 458 public final String toString() { 459 // This returns the compiler name to simplify parameterization naming in @JavacCompilerTest 460 // parameterized tests. 461 return name; 462 } 463 464 /** 465 * Get the flag builder factory to use for building flags. 466 * 467 * @return the factory. 468 */ 469 public abstract JctFlagBuilderFactory getFlagBuilderFactory(); 470 471 /** 472 * Get the JSR-199 compiler factory to use for initialising an internal compiler. 473 * 474 * @return the factory. 475 */ 476 public abstract Jsr199CompilerFactory getCompilerFactory(); 477 478 /** 479 * Get the file manager factory to use for building AbstractJctCompiler file manager during 480 * compilation. 481 * 482 * <p>Since v1.1.0, this method has provided a default implementation. Before this, it was 483 * abstract. The default implementation calls 484 * {@link JctFileManagers#newJctFileManagerFactory(JctCompiler)}. 485 * 486 * @return the factory. 487 */ 488 public JctFileManagerFactory getFileManagerFactory() { 489 return JctFileManagers.newJctFileManagerFactory(this); 490 } 491 492 /** 493 * Get the compilation factory to use for building a compilation. 494 * 495 * <p>By default, this uses a common internal implementation that is designed to work with 496 * compilers that have interfaces the same as, and behave the same as Javac. 497 * 498 * <p>Some obscure compiler implementations with potentially satanic rituals for initialising 499 * and configuring components correctly may need to provide a custom implementation here instead. 500 * In this case, this method should be overridden. Base classes are not provided for you to extend 501 * in this case as this is usually not something you want to be doing. Instead, you should 502 * implement {@link JctCompilationFactory} directly. 503 * 504 * @return the compilation factory. 505 */ 506 public JctCompilationFactory getCompilationFactory() { 507 return new JctCompilationFactoryImpl(this); 508 } 509 510 /** 511 * {@inheritDoc} 512 * 513 * @return the default release version to use when no version is specified by the user. 514 */ 515 @Override 516 public abstract String getDefaultRelease(); 517 518 /** 519 * Build the list of flags from this compiler object using the flag builder. 520 * 521 * <p>Implementations should not need to override this unless there is a special edge case 522 * that needs configuring differently. This is exposed to assist in these kinds of cases. 523 * 524 * @param flagBuilder the flag builder to apply the flag configuration to. 525 * @return the string flags to use. 526 */ 527 protected List<String> buildFlags(JctFlagBuilder flagBuilder) { 528 return flagBuilder 529 .annotationProcessorOptions(annotationProcessorOptions) 530 .showDeprecationWarnings(showDeprecationWarnings) 531 .failOnWarnings(failOnWarnings) 532 .compilerOptions(compilerOptions) 533 .previewFeatures(previewFeatures) 534 .release(release) 535 .source(source) 536 .target(target) 537 .verbose(verbose) 538 .showWarnings(showWarnings) 539 .debuggingInfo(debuggingInfo) 540 .parameterInfoEnabled(parameterInfoEnabled) 541 .build(); 542 } 543 544 @SuppressWarnings("ThrowFromFinallyBlock") 545 private JctCompilation performCompilation( 546 Workspace workspace, 547 @Nullable Collection<String> classNames 548 ) { 549 var fileManagerFactory = getFileManagerFactory(); 550 var flagBuilderFactory = getFlagBuilderFactory(); 551 var compilerFactory = getCompilerFactory(); 552 var compilationFactory = getCompilationFactory(); 553 var flags = buildFlags(flagBuilderFactory.createFlagBuilder()); 554 var compiler = compilerFactory.createCompiler(); 555 var fileManager = fileManagerFactory.createFileManager(workspace); 556 557 // Any internal exceptions should be rethrown as a JctCompilerException by the 558 // compilation factory, so there is nothing else to worry about here. 559 // Likewise, do not catch IOException on the compilation process, as it may hide 560 // bugs. 561 // 562 // The try-finally-try-catch-rethrow ensures we only catch IOExceptions during 563 // the file manager closure, where it is a bug. 564 565 try { 566 return compilationFactory.createCompilation(flags, fileManager, compiler, classNames); 567 } finally { 568 try { 569 fileManager.close(); 570 } catch (IOException ex) { 571 throw new JctCompilerException( 572 "Failed to close file manager. This is probably a bug, so please report it.", 573 ex 574 ); 575 } 576 } 577 } 578}