001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.codegen; 007 008import gov.nist.secauto.metaschema.core.model.IModule; 009import gov.nist.secauto.metaschema.core.util.ObjectUtils; 010import gov.nist.secauto.metaschema.databind.IBindingContext; 011import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration; 012import gov.nist.secauto.metaschema.databind.codegen.config.IBindingConfiguration; 013 014import org.apache.logging.log4j.LogManager; 015import org.apache.logging.log4j.Logger; 016import org.eclipse.jdt.annotation.Owning; 017 018import java.io.IOException; 019import java.lang.module.ModuleDescriptor; 020import java.net.MalformedURLException; 021import java.net.URL; 022import java.net.URLClassLoader; 023import java.nio.file.Path; 024import java.security.AccessController; 025import java.security.PrivilegedAction; 026import java.util.Arrays; 027import java.util.List; 028import java.util.stream.Collectors; 029 030import javax.tools.DiagnosticCollector; 031 032import edu.umd.cs.findbugs.annotations.NonNull; 033 034/** 035 * This class provides methods to generate and dynamically compile Java code 036 * based on a Module. The {@link #newClassLoader(Path, ClassLoader)} method can 037 * be used to get a {@link ClassLoader} for Java code previously generated by 038 * this class. 039 */ 040public final class ModuleCompilerHelper { 041 private static final Logger LOGGER = LogManager.getLogger(ModuleCompilerHelper.class); 042 043 private ModuleCompilerHelper() { 044 // disable construction 045 } 046 047 /** 048 * Create a new classloader capable of loading Java classes generated in the 049 * provided {@code classDir}. 050 * 051 * @param classDir 052 * the directory where generated Java classes have been compiled 053 * @param parent 054 * the classloader to delegate to when the created class loader cannot 055 * load a class 056 * @return the new class loader 057 */ 058 @SuppressWarnings("resource") 059 @Owning 060 @NonNull 061 public static ClassLoader newClassLoader( 062 @NonNull final Path classDir, 063 @NonNull final ClassLoader parent) { 064 return ObjectUtils.notNull(AccessController.doPrivileged( 065 (PrivilegedAction<ClassLoader>) () -> { 066 try { 067 return new URLClassLoader(new URL[] { classDir.toUri().toURL() }, parent); 068 } catch (MalformedURLException ex) { 069 throw new IllegalStateException("unable to configure class loader", ex); 070 } 071 })); 072 } 073 074 /** 075 * Generate and compile Java class, representing the provided Module 076 * {@code module} and its related definitions, using the default binding 077 * configuration. 078 * 079 * @param module 080 * the Module module to generate Java classes for 081 * @param classDir 082 * the directory to generate the classes in 083 * @return information about the generated classes 084 * @throws IOException 085 * if an error occurred while generating or compiling the classes 086 */ 087 @NonNull 088 public static IProduction compileMetaschema( 089 @NonNull IModule module, 090 @NonNull Path classDir) 091 throws IOException { 092 return compileModule(module, classDir, new DefaultBindingConfiguration()); 093 } 094 095 /** 096 * Generate and compile Java class, representing the provided Module 097 * {@code module} and its related definitions, using the provided custom 098 * {@code bindingConfiguration}. 099 * 100 * @param module 101 * the Module module to generate Java classes for 102 * @param classDir 103 * the directory to generate the classes in 104 * @param bindingConfiguration 105 * configuration settings with directives that tailor the class 106 * generation 107 * @return information about the generated classes 108 * @throws IOException 109 * if an error occurred while generating or compiling the classes 110 */ 111 @NonNull 112 public static IProduction compileModule( 113 @NonNull IModule module, 114 @NonNull Path classDir, 115 @NonNull IBindingConfiguration bindingConfiguration) throws IOException { 116 IProduction production = JavaGenerator.generate(module, classDir, bindingConfiguration); 117 List<IGeneratedClass> classesToCompile = production.getGeneratedClasses().collect(Collectors.toList()); 118 119 List<Path> classes = ObjectUtils.notNull(classesToCompile.stream() 120 .map(IGeneratedClass::getClassFile) 121 .collect(Collectors.toUnmodifiableList())); 122 123 // configure the compiler 124 JavaCompilerSupport compiler = new JavaCompilerSupport(classDir); 125 126 // determine if we need to use the module path 127 boolean useModulePath = false; 128 Module databindModule = IBindingContext.class.getModule(); 129 if (databindModule != null) { 130 ModuleDescriptor descriptor = databindModule.getDescriptor(); 131 if (descriptor != null) { 132 // add the databind module to the task 133 compiler.addRootModule(ObjectUtils.notNull(descriptor.name())); 134 useModulePath = true; 135 } 136 } 137 138 handleClassAndModulePath(compiler, useModulePath); 139 140 // perform compilation 141 JavaCompilerSupport.CompilationResult result = compiler.compile(classes, null); 142 143 if (!result.isSuccessful()) { 144 // log compilation diagnostics 145 DiagnosticCollector<?> diagnostics = new DiagnosticCollector<>(); 146 if (LOGGER.isErrorEnabled()) { 147 LOGGER.error(diagnostics.getDiagnostics().toString()); 148 } 149 throw new IllegalStateException(String.format("failed to compile classes: %s%nClasspath: %s%nModule Path: %s%n%s", 150 classesToCompile.stream() 151 .map(clazz -> clazz.getClassName().canonicalName()) 152 .collect(Collectors.joining(",")), 153 diagnostics.getDiagnostics().toString(), 154 compiler.getClassPath().stream() 155 .collect(Collectors.joining(":")), 156 compiler.getModulePath().stream() 157 .collect(Collectors.joining(":")))); 158 } 159 return production; 160 } 161 162 private static void handleClassAndModulePath(JavaCompilerSupport compiler, boolean useModulePath) { 163 String classPath = System.getProperty("java.class.path"); 164 String modulePath = System.getProperty("jdk.module.path"); 165 if (useModulePath) { 166 // use classpath and modulepath from the JDK 167 if (classPath != null) { 168 Arrays.stream(classPath.split(":")).forEachOrdered(compiler::addToClassPath); 169 } 170 171 if (modulePath != null) { 172 Arrays.stream(modulePath.split(":")).forEachOrdered(compiler::addToModulePath); 173 } 174 } else { 175 // use classpath only 176 if (classPath != null) { 177 Arrays.stream(classPath.split(":")).forEachOrdered(compiler::addToClassPath); 178 } 179 180 if (modulePath != null) { 181 Arrays.stream(modulePath.split(":")).forEachOrdered(compiler::addToClassPath); 182 } 183 } 184 185 } 186}