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