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> thread-safe! 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 = JctCompiler.DEFAULT_SHOW_WARNINGS; 100 showDeprecationWarnings = JctCompiler.DEFAULT_SHOW_DEPRECATION_WARNINGS; 101 failOnWarnings = JctCompiler.DEFAULT_FAIL_ON_WARNINGS; 102 compilationMode = JctCompiler.DEFAULT_COMPILATION_MODE; 103 locale = JctCompiler.DEFAULT_LOCALE; 104 logCharset = JctCompiler.DEFAULT_LOG_CHARSET; 105 previewFeatures = JctCompiler.DEFAULT_PREVIEW_FEATURES; 106 verbose = JctCompiler.DEFAULT_VERBOSE; 107 diagnosticLoggingMode = JctCompiler.DEFAULT_DIAGNOSTIC_LOGGING_MODE; 108 fixJvmModulePathMismatch = JctCompiler.DEFAULT_FIX_JVM_MODULE_PATH_MISMATCH; 109 inheritClassPath = JctCompiler.DEFAULT_INHERIT_CLASS_PATH; 110 inheritModulePath = JctCompiler.DEFAULT_INHERIT_MODULE_PATH; 111 inheritSystemModulePath = JctCompiler.DEFAULT_INHERIT_SYSTEM_MODULE_PATH; 112 fileManagerLoggingMode = JctCompiler.DEFAULT_FILE_MANAGER_LOGGING_MODE; 113 annotationProcessorDiscovery = JctCompiler.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 @Override 278 public String getRelease() { 279 return release; 280 } 281 282 @Override 283 public AbstractJctCompiler release(@Nullable String release) { 284 this.release = release; 285 286 if (release != null) { 287 source = null; 288 target = null; 289 } 290 291 return this; 292 } 293 294 @Override 295 public String getSource() { 296 return source; 297 } 298 299 @Override 300 public AbstractJctCompiler source(@Nullable String source) { 301 this.source = source; 302 if (source != null) { 303 release = null; 304 } 305 return this; 306 } 307 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}