001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.databind.codegen; 028 029import gov.nist.secauto.metaschema.core.model.IModule; 030import gov.nist.secauto.metaschema.databind.IBindingContext; 031import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration; 032import gov.nist.secauto.metaschema.databind.codegen.config.IBindingConfiguration; 033 034import org.apache.logging.log4j.LogManager; 035import org.apache.logging.log4j.Logger; 036 037import java.io.IOException; 038import java.lang.module.ModuleDescriptor; 039import java.net.MalformedURLException; 040import java.net.URL; 041import java.net.URLClassLoader; 042import java.nio.file.Path; 043import java.security.AccessController; 044import java.security.PrivilegedAction; 045import java.util.ArrayList; 046import java.util.LinkedList; 047import java.util.List; 048import java.util.stream.Collectors; 049 050import javax.tools.DiagnosticCollector; 051import javax.tools.JavaCompiler; 052import javax.tools.JavaFileManager; 053import javax.tools.JavaFileObject; 054import javax.tools.StandardJavaFileManager; 055import javax.tools.ToolProvider; 056 057import edu.umd.cs.findbugs.annotations.NonNull; 058 059/** 060 * This class provides methods to generate and dynamically compile Java code 061 * based on a Module. The {@link #newClassLoader(Path, ClassLoader)} method can 062 * be used to get a {@link ClassLoader} for Java code previously generated by 063 * this class. 064 */ 065public final class ModuleCompilerHelper { 066 private static final Logger LOGGER = LogManager.getLogger(ModuleCompilerHelper.class); 067 068 private ModuleCompilerHelper() { 069 // disable construction 070 } 071 072 /** 073 * Generate and compile Java class, representing the provided Module 074 * {@code module} and its related definitions, using the default binding 075 * configuration. 076 * 077 * @param module 078 * the Module module to generate Java classes for 079 * @param classDir 080 * the directory to generate the classes in 081 * @return information about the generated classes 082 * @throws IOException 083 * if an error occurred while generating or compiling the classes 084 */ 085 @NonNull 086 public static IProduction compileMetaschema( 087 @NonNull IModule module, 088 @NonNull Path classDir) 089 throws IOException { 090 return compileModule(module, classDir, new DefaultBindingConfiguration()); 091 } 092 093 /** 094 * Generate and compile Java class, representing the provided Module 095 * {@code module} and its related definitions, using the provided custom 096 * {@code bindingConfiguration}. 097 * 098 * @param module 099 * the Module module to generate Java classes for 100 * @param classDir 101 * the directory to generate the classes in 102 * @param bindingConfiguration 103 * configuration settings with directives that tailor the class 104 * generation 105 * @return information about the generated classes 106 * @throws IOException 107 * if an error occurred while generating or compiling the classes 108 */ 109 @NonNull 110 public static IProduction compileModule( 111 @NonNull IModule module, 112 @NonNull Path classDir, 113 @NonNull IBindingConfiguration bindingConfiguration) throws IOException { 114 IProduction production = JavaGenerator.generate(module, classDir, bindingConfiguration); 115 List<IGeneratedClass> classesToCompile = production.getGeneratedClasses().collect(Collectors.toList()); 116 117 DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); 118 if (!compileGeneratedClasses(classesToCompile, diagnostics, classDir)) { 119 if (LOGGER.isErrorEnabled()) { 120 LOGGER.error(diagnostics.getDiagnostics().toString()); 121 } 122 throw new IllegalStateException(String.format("failed to compile classes: %s", 123 classesToCompile.stream() 124 .map(clazz -> clazz.getClassName().canonicalName()) 125 .collect(Collectors.joining(",")))); 126 } 127 return production; 128 } 129 130 /** 131 * Create a new classloader capable of loading Java classes generated in the 132 * provided {@code classDir}. 133 * 134 * @param classDir 135 * the directory where generated Java classes have been compiled 136 * @param parent 137 * the classloader to delegate to when the created class loader cannot 138 * load a class 139 * @return the new class loader 140 */ 141 @SuppressWarnings("null") 142 @NonNull 143 public static ClassLoader newClassLoader( 144 @NonNull final Path classDir, 145 @NonNull final ClassLoader parent) { 146 return AccessController.doPrivileged(new PrivilegedAction<URLClassLoader>() { 147 @Override 148 public URLClassLoader run() { 149 try { 150 return new URLClassLoader(new URL[] { classDir.toUri().toURL() }, parent); 151 } catch (MalformedURLException ex) { 152 throw new IllegalStateException("unable to configure class loader", ex); 153 } 154 } 155 }); 156 } 157 158 @SuppressWarnings({ 159 "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity", // acceptable 160 }) 161 private static boolean compile( 162 JavaCompiler compiler, 163 JavaFileManager fileManager, 164 DiagnosticCollector<JavaFileObject> diagnostics, 165 List<JavaFileObject> compilationUnits, 166 Path classDir) { 167 168 String moduleName = null; 169 Module module = IBindingContext.class.getModule(); 170 if (module != null) { 171 ModuleDescriptor descriptor = module.getDescriptor(); 172 if (descriptor != null) { 173 // add the databind module to the task 174 moduleName = descriptor.name(); 175 } 176 } 177 178 List<String> options = new LinkedList<>(); 179 // options.add("-verbose"); 180 // options.add("-g"); 181 options.add("-d"); 182 options.add(classDir.toString()); 183 184 String classPath = System.getProperty("java.class.path"); 185 String modulePath = System.getProperty("jdk.module.path"); 186 if (moduleName == null) { 187 // use classpath only 188 String path = null; 189 if (classPath != null) { 190 path = classPath; 191 } 192 193 if (modulePath != null) { 194 path = path == null ? modulePath : path + ":" + modulePath; 195 } 196 197 if (path != null) { 198 options.add("-classpath"); 199 options.add(path); 200 } 201 } else { 202 // use classpath and modulepath from the JDK 203 if (classPath != null) { 204 options.add("-classpath"); 205 options.add(classPath); 206 } 207 208 if (modulePath != null) { 209 options.add("-p"); 210 options.add(modulePath); 211 } 212 } 213 214 if (LOGGER.isDebugEnabled()) { 215 LOGGER.atDebug().log("Using options: {}", options); 216 } 217 218 JavaCompiler.CompilationTask task 219 = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); 220 221 if (moduleName != null) { 222 task.addModules(List.of(moduleName)); 223 } 224 return task.call(); 225 } 226 227 private static boolean compileGeneratedClasses( 228 List<IGeneratedClass> classesToCompile, 229 DiagnosticCollector<JavaFileObject> diagnostics, 230 Path classDir) throws IOException { 231 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 232 233 try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { 234 235 List<JavaFileObject> compilationUnits = new ArrayList<>(classesToCompile.size()); 236 for (IGeneratedClass generatedClass : classesToCompile) { 237 compilationUnits.add(fileManager.getJavaFileObjects(generatedClass.getClassFile()).iterator().next()); 238 } 239 240 return compile(compiler, fileManager, diagnostics, compilationUnits, classDir); 241 } 242 } 243}