001/* 002 * JDrupes Builder 003 * Copyright (C) 2025 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.java; 020 021import com.google.common.flogger.FluentLogger; 022import java.nio.file.Path; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.List; 026import java.util.Locale; 027import java.util.Optional; 028import java.util.stream.Stream; 029import javax.tools.Diagnostic; 030import javax.tools.DiagnosticCollector; 031import javax.tools.JavaFileObject; 032import org.jdrupes.builder.api.Project; 033import org.jdrupes.builder.core.AbstractGenerator; 034 035/// A base class for generators that invoke java tools. 036/// 037public abstract class JavaTool extends AbstractGenerator { 038 039 /// Controls how Java compilation handles JPMS modules. 040 /// 041 public enum ModuleMode { 042 /// Always use classpath compilation. 043 CLASSPATH, 044 /// Always use module-path compilation for modular dependencies. 045 MODULE, 046 /// Use module-path compilation when sources contain 047 /// `module-info.java`, otherwise fall back to classpath. 048 AUTO 049 } 050 051 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 052 private JavaTool.ModuleMode moduleMode = JavaTool.ModuleMode.AUTO; 053 private final List<String> options = new ArrayList<>(); 054 055 /// Instantiates a new java tool. 056 /// 057 /// @param project the project 058 /// 059 public JavaTool(Project project) { 060 super(project); 061 } 062 063 /// Returns the current module compilation mode. Defaults to 064 /// `ModuleMode.AUTO`, which activates module-path compilation when 065 /// a `module-info.java` is found among the sources. 066 /// 067 /// @return the module mode 068 /// 069 public JavaTool.ModuleMode moduleMode() { 070 return moduleMode; 071 } 072 073 /// Sets the module compilation mode. 074 /// 075 /// @param moduleMode the new mode 076 /// @return the java compiler 077 /// 078 public JavaTool moduleMode(JavaTool.ModuleMode moduleMode) { 079 this.moduleMode = moduleMode; 080 return this; 081 } 082 083 /// Determines the effective module mode, resolving `ModuleMode.AUTO` 084 /// to either `ModuleMode.MODULE` or `ModuleMode.CLASSPATH` based on 085 /// whether a `module-info.java` exists among the sources. 086 /// 087 /// @return the effective mode 088 /// 089 protected ModuleMode effectiveModuleMode() { 090 if (moduleMode == JavaTool.ModuleMode.AUTO) { 091 return hasModuleInfo() ? JavaTool.ModuleMode.MODULE 092 : JavaTool.ModuleMode.CLASSPATH; 093 } 094 return moduleMode; 095 } 096 097 /// Checks whether a `module-info.java` exists among the configured 098 /// sources. 099 /// 100 /// @return true, if module-info.java is present 101 /// 102 protected abstract boolean hasModuleInfo(); 103 104 /// Adds the given options. 105 /// 106 /// @param options the options 107 /// @return the java tool 108 /// 109 public JavaTool options(Stream<String> options) { 110 this.options.addAll(options.toList()); 111 return this; 112 } 113 114 /// Adds the given options. 115 /// 116 /// @param options the options 117 /// @return the java tool 118 /// 119 public JavaTool options(String... options) { 120 this.options.addAll(Arrays.asList(options)); 121 return this; 122 } 123 124 /// Return the options. 125 /// 126 /// @return the stream 127 /// 128 public List<String> options() { 129 return options; 130 } 131 132 /// Find the argument for the given option. As some options are 133 /// allows in different styles, several names can be specified. 134 /// 135 /// @param names the names 136 /// @return the optional 137 /// 138 public Optional<String> optionArgument(String... names) { 139 var itr = options.iterator(); 140 if (itr.hasNext()) { 141 String opt = itr.next(); 142 if (Arrays.stream(names).anyMatch(opt::equals) && itr.hasNext()) { 143 return Optional.of(itr.next()); 144 } 145 } 146 return Optional.empty(); 147 } 148 149 /// Log diagnostic. 150 /// 151 /// @param diagnostic the diagnostic 152 /// 153 protected void logDiagnostic( 154 Diagnostic<? extends JavaFileObject> diagnostic) { 155 String level = switch (diagnostic.getKind()) { 156 case ERROR -> "error"; 157 case WARNING -> "warning"; 158 case MANDATORY_WARNING -> "warning"; 159 default -> "info"; 160 }; 161 String msg; 162 if (diagnostic.getSource() == null) { 163 msg = level + ": " + diagnostic.getMessage(Locale.ENGLISH); 164 } else { 165 msg = String.format("%s:%d:%d: %s: %s", 166 project().rootProject().directory().relativize( 167 Path.of(diagnostic.getSource().toUri().getPath())), 168 diagnostic.getLineNumber(), diagnostic.getColumnNumber(), 169 level, diagnostic.getMessage(null)); 170 } 171 switch (diagnostic.getKind()) { 172 case ERROR -> logger.atSevere().log(msg); 173 case WARNING -> logger.atWarning().log(msg); 174 case MANDATORY_WARNING -> logger.atWarning().log(msg); 175 default -> logger.atInfo().log(msg); 176 } 177 if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { 178 context().error().println(msg); 179 } else { 180 context().out().println(msg); 181 } 182 } 183 184 /// Log diagnostics. 185 /// 186 /// @param diagnostics the diagnostics 187 /// 188 protected void 189 logDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics) { 190 if (diagnostics.getDiagnostics().isEmpty()) { 191 return; 192 } 193 context().out().println("Problems found by " + this + ":"); 194 for (var diagnostic : diagnostics.getDiagnostics()) { 195 logDiagnostic(diagnostic); 196 } 197 } 198}