001/* 002 * Copyright (C) 2022 - 2023, 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.LoggingMode; 027import io.github.ascopes.jct.workspaces.Workspace; 028import java.io.IOException; 029import java.nio.charset.Charset; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.List; 033import java.util.Locale; 034import javax.annotation.processing.Processor; 035import org.apiguardian.api.API; 036import org.apiguardian.api.API.Status; 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</strong> thread-safe. 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 * @param <A> the type of the class extending this class. 054 * @author Ashley Scopes 055 * @since 0.0.1 056 */ 057@API(since = "0.0.1", status = Status.STABLE) 058public abstract class AbstractJctCompiler<A extends AbstractJctCompiler<A>> 059 implements JctCompiler<A, JctCompilation> { 060 061 private final List<Processor> annotationProcessors; 062 private final List<String> annotationProcessorOptions; 063 private final List<String> compilerOptions; 064 private String name; 065 private boolean showWarnings; 066 private boolean showDeprecationWarnings; 067 private boolean failOnWarnings; 068 private CompilationMode compilationMode; 069 private Locale locale; 070 private Charset logCharset; 071 private boolean verbose; 072 private boolean previewFeatures; 073 private @Nullable String release; 074 private @Nullable String source; 075 private @Nullable String target; 076 private LoggingMode diagnosticLoggingMode; 077 private boolean fixJvmModulePathMismatch; 078 private boolean inheritClassPath; 079 private boolean inheritModulePath; 080 private boolean inheritPlatformClassPath; 081 private boolean inheritSystemModulePath; 082 private LoggingMode fileManagerLoggingMode; 083 private AnnotationProcessorDiscovery annotationProcessorDiscovery; 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 annotationProcessors = new ArrayList<>(); 093 annotationProcessorOptions = new ArrayList<>(); 094 compilerOptions = new ArrayList<>(); 095 showWarnings = JctCompiler.DEFAULT_SHOW_WARNINGS; 096 showDeprecationWarnings = JctCompiler.DEFAULT_SHOW_DEPRECATION_WARNINGS; 097 failOnWarnings = JctCompiler.DEFAULT_FAIL_ON_WARNINGS; 098 compilationMode = JctCompiler.DEFAULT_COMPILATION_MODE; 099 locale = JctCompiler.DEFAULT_LOCALE; 100 logCharset = JctCompiler.DEFAULT_LOG_CHARSET; 101 previewFeatures = JctCompiler.DEFAULT_PREVIEW_FEATURES; 102 release = null; 103 source = null; 104 target = null; 105 verbose = JctCompiler.DEFAULT_VERBOSE; 106 diagnosticLoggingMode = JctCompiler.DEFAULT_DIAGNOSTIC_LOGGING_MODE; 107 fixJvmModulePathMismatch = JctCompiler.DEFAULT_FIX_JVM_MODULE_PATH_MISMATCH; 108 inheritClassPath = JctCompiler.DEFAULT_INHERIT_CLASS_PATH; 109 inheritModulePath = JctCompiler.DEFAULT_INHERIT_MODULE_PATH; 110 inheritPlatformClassPath = JctCompiler.DEFAULT_INHERIT_PLATFORM_CLASS_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 } 115 116 @Override 117 public JctCompilation compile(Workspace workspace) { 118 return compileInternal(workspace, null); 119 } 120 121 @Override 122 public JctCompilation compile(Workspace workspace, Collection<String> classNames) { 123 return compileInternal(workspace, classNames); 124 } 125 126 @Override 127 public final <E extends Exception> A configure(JctCompilerConfigurer<E> configurer) throws E { 128 requireNonNull(configurer, "configurer"); 129 configurer.configure(this); 130 return myself(); 131 } 132 133 @Override 134 public String getName() { 135 return name; 136 } 137 138 @Override 139 public A name(String name) { 140 this.name = requireNonNull(name, "name"); 141 return myself(); 142 } 143 144 @Override 145 public boolean isVerbose() { 146 return verbose; 147 } 148 149 @Override 150 public A verbose(boolean enabled) { 151 verbose = enabled; 152 return myself(); 153 } 154 155 @Override 156 public boolean isPreviewFeatures() { 157 return previewFeatures; 158 } 159 160 @Override 161 public A previewFeatures(boolean enabled) { 162 previewFeatures = enabled; 163 return myself(); 164 } 165 166 @Override 167 public boolean isShowWarnings() { 168 return showWarnings; 169 } 170 171 @Override 172 public A showWarnings(boolean enabled) { 173 showWarnings = enabled; 174 return myself(); 175 } 176 177 @Override 178 public boolean isShowDeprecationWarnings() { 179 return showDeprecationWarnings; 180 } 181 182 @Override 183 public A showDeprecationWarnings(boolean enabled) { 184 showDeprecationWarnings = enabled; 185 return myself(); 186 } 187 188 @Override 189 public boolean isFailOnWarnings() { 190 return failOnWarnings; 191 } 192 193 @Override 194 public A failOnWarnings(boolean enabled) { 195 failOnWarnings = enabled; 196 return myself(); 197 } 198 199 @Override 200 public CompilationMode getCompilationMode() { 201 return compilationMode; 202 } 203 204 @Override 205 public A compilationMode(CompilationMode compilationMode) { 206 this.compilationMode = compilationMode; 207 return myself(); 208 } 209 210 @Override 211 public List<String> getAnnotationProcessorOptions() { 212 return List.copyOf(annotationProcessorOptions); 213 } 214 215 @Override 216 public A addAnnotationProcessorOptions(Iterable<String> annotationProcessorOptions) { 217 requireNonNullValues(annotationProcessorOptions, "annotationProcessorOptions"); 218 annotationProcessorOptions.forEach(this.annotationProcessorOptions::add); 219 return myself(); 220 } 221 222 @Override 223 public List<Processor> getAnnotationProcessors() { 224 return List.copyOf(annotationProcessors); 225 } 226 227 @Override 228 public A addAnnotationProcessors(Iterable<? extends Processor> annotationProcessors) { 229 requireNonNullValues(annotationProcessors, "annotationProcessors"); 230 annotationProcessors.forEach(this.annotationProcessors::add); 231 232 return myself(); 233 } 234 235 @Override 236 public List<String> getCompilerOptions() { 237 return List.copyOf(compilerOptions); 238 } 239 240 @Override 241 public A addCompilerOptions(Iterable<String> compilerOptions) { 242 requireNonNullValues(compilerOptions, "compilerOptions"); 243 compilerOptions.forEach(this.compilerOptions::add); 244 return myself(); 245 } 246 247 @Override 248 public String getEffectiveRelease() { 249 if (release != null) { 250 return release; 251 } 252 253 if (target != null) { 254 return target; 255 } 256 257 return getDefaultRelease(); 258 } 259 260 @Override 261 public String getRelease() { 262 return release; 263 } 264 265 @Override 266 public A release(@Nullable String release) { 267 this.release = release; 268 269 if (release != null) { 270 source = null; 271 target = null; 272 } 273 274 return myself(); 275 } 276 277 @Override 278 public String getSource() { 279 return source; 280 } 281 282 @Override 283 public A source(@Nullable String source) { 284 this.source = source; 285 if (source != null) { 286 release = null; 287 } 288 return myself(); 289 } 290 291 @Override 292 public String getTarget() { 293 return target; 294 } 295 296 @Override 297 public A target(@Nullable String target) { 298 this.target = target; 299 if (target != null) { 300 release = null; 301 } 302 return myself(); 303 } 304 305 @Override 306 public boolean isFixJvmModulePathMismatch() { 307 return fixJvmModulePathMismatch; 308 } 309 310 @Override 311 public A fixJvmModulePathMismatch(boolean fixJvmModulePathMismatch) { 312 this.fixJvmModulePathMismatch = fixJvmModulePathMismatch; 313 return myself(); 314 } 315 316 @Override 317 public boolean isInheritClassPath() { 318 return inheritClassPath; 319 } 320 321 @Override 322 public A inheritClassPath(boolean inheritClassPath) { 323 this.inheritClassPath = inheritClassPath; 324 return myself(); 325 } 326 327 @Override 328 public boolean isInheritModulePath() { 329 return inheritModulePath; 330 } 331 332 @Override 333 public A inheritModulePath(boolean inheritModulePath) { 334 this.inheritModulePath = inheritModulePath; 335 return myself(); 336 } 337 338 @Override 339 public boolean isInheritPlatformClassPath() { 340 return inheritPlatformClassPath; 341 } 342 343 @Override 344 public A inheritPlatformClassPath(boolean inheritPlatformClassPath) { 345 this.inheritPlatformClassPath = inheritPlatformClassPath; 346 return myself(); 347 } 348 349 @Override 350 public boolean isInheritSystemModulePath() { 351 return inheritSystemModulePath; 352 } 353 354 @Override 355 public A inheritSystemModulePath(boolean inheritSystemModulePath) { 356 this.inheritSystemModulePath = inheritSystemModulePath; 357 return myself(); 358 } 359 360 @Override 361 public Locale getLocale() { 362 return locale; 363 } 364 365 @Override 366 public A locale(Locale locale) { 367 requireNonNull(locale, "locale"); 368 this.locale = locale; 369 return myself(); 370 } 371 372 @Override 373 public Charset getLogCharset() { 374 return logCharset; 375 } 376 377 @Override 378 public A logCharset(Charset logCharset) { 379 requireNonNull(logCharset, "logCharset"); 380 this.logCharset = logCharset; 381 return myself(); 382 } 383 384 @Override 385 public LoggingMode getFileManagerLoggingMode() { 386 return fileManagerLoggingMode; 387 } 388 389 @Override 390 public A fileManagerLoggingMode(LoggingMode fileManagerLoggingMode) { 391 this.fileManagerLoggingMode = requireNonNull(fileManagerLoggingMode, "fileManagerLoggingMode"); 392 return myself(); 393 } 394 395 @Override 396 public LoggingMode getDiagnosticLoggingMode() { 397 return diagnosticLoggingMode; 398 } 399 400 @Override 401 public A diagnosticLoggingMode(LoggingMode diagnosticLoggingMode) { 402 requireNonNull(diagnosticLoggingMode, "diagnosticLoggingMode"); 403 this.diagnosticLoggingMode = diagnosticLoggingMode; 404 return myself(); 405 } 406 407 @Override 408 public AnnotationProcessorDiscovery getAnnotationProcessorDiscovery() { 409 return annotationProcessorDiscovery; 410 } 411 412 @Override 413 public A annotationProcessorDiscovery(AnnotationProcessorDiscovery annotationProcessorDiscovery) { 414 this.annotationProcessorDiscovery = requireNonNull( 415 annotationProcessorDiscovery, 416 "annotationProcessorDiscovery" 417 ); 418 return myself(); 419 } 420 421 /** 422 * Get the compiler name. 423 * 424 * @return the compiler name. 425 * @see #getName() 426 * @see #name(String) 427 * @deprecated Use {@link #getName()} instead. 428 */ 429 @Deprecated 430 @Override 431 public final String toString() { 432 return name; 433 } 434 435 /** 436 * Get the flag builder factory to use for building flags. 437 * 438 * @return the factory. 439 */ 440 public abstract JctFlagBuilderFactory getFlagBuilderFactory(); 441 442 /** 443 * Get the JSR-199 compiler factory to use for initialising an internal compiler. 444 * 445 * @return the factory. 446 */ 447 public abstract Jsr199CompilerFactory getCompilerFactory(); 448 449 /** 450 * Get the file manager factory to use for building a file manager during compilation. 451 * 452 * @return the factory. 453 */ 454 public abstract JctFileManagerFactory getFileManagerFactory(); 455 456 /** 457 * Get the compilation factory to use for building a compilation. 458 * 459 * <p>By default, this uses a common internal implementation that is designed to work with 460 * compilers that have interfaces the same as, and behave the same as Javac. 461 * 462 * <p>Some obscure compiler implementations with potentially satanic rituals for initialising 463 * and configuring components correctly may need to provide a custom implementation here instead. 464 * In this case, this method should be overridden. 465 * 466 * @return the compilation factory. 467 */ 468 public JctCompilationFactory getCompilationFactory() { 469 return new JctCompilationFactoryImpl(this); 470 } 471 472 /** 473 * {@inheritDoc} 474 * 475 * @return the default release version to use when no version is specified by the user. 476 */ 477 @Override 478 public abstract String getDefaultRelease(); 479 480 /** 481 * Get this implementation of {@link AbstractJctCompiler}, cast to the type parameter {@link A}. 482 * 483 * @return this implementation of {@link AbstractJctCompiler}, cast to {@link A}. 484 */ 485 protected final A myself() { 486 @SuppressWarnings("unchecked") 487 var me = (A) this; 488 489 return me; 490 } 491 492 /** 493 * Build the list of flags from this compiler object using the flag builder. 494 * 495 * <p>Implementations should not need to override this unless there is a special edge case 496 * that needs configuring differently. This is exposed to assist in these kinds of cases. 497 * 498 * @param flagBuilder the flag builder to apply the flag configuration to. 499 * @return the string flags to use. 500 */ 501 protected List<String> buildFlags(JctFlagBuilder flagBuilder) { 502 return flagBuilder 503 .annotationProcessorOptions(annotationProcessorOptions) 504 .showDeprecationWarnings(showDeprecationWarnings) 505 .failOnWarnings(failOnWarnings) 506 .compilerOptions(compilerOptions) 507 .previewFeatures(previewFeatures) 508 .release(release) 509 .source(source) 510 .target(target) 511 .verbose(verbose) 512 .showWarnings(showWarnings) 513 .build(); 514 } 515 516 @SuppressWarnings("ThrowFromFinallyBlock") 517 private JctCompilation compileInternal(Workspace workspace, Collection<String> classNames) { 518 var fileManagerFactory = getFileManagerFactory(); 519 var flagBuilderFactory = getFlagBuilderFactory(); 520 var compilerFactory = getCompilerFactory(); 521 var compilationFactory = getCompilationFactory(); 522 var flags = buildFlags(flagBuilderFactory.createFlagBuilder()); 523 var compiler = compilerFactory.createCompiler(); 524 var fileManager = fileManagerFactory.createFileManager(workspace); 525 526 // Any internal exceptions should be rethrown as a JctCompilerException by the 527 // compilation factory, so there is nothing else to worry about here. 528 // Likewise, do not catch IOException on the compilation process, as it may hide 529 // bugs. Hence, we have to use a try-finally-try-catch-rethrow manually rather than a nice 530 // try-with-resources. This is kinda crap code, but it prevents reporting errors incorrectly. 531 532 try { 533 return compilationFactory.createCompilation(flags, fileManager, compiler, classNames); 534 } finally { 535 try { 536 fileManager.close(); 537 } catch (IOException ex) { 538 throw new JctCompilerException( 539 "Failed to close file manager. This is probably a bug, so please report it.", 540 ex 541 ); 542 } 543 } 544 } 545}