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