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 JavaCompilerSupport compiler = new JavaCompilerSupport(classDir); 124 125 boolean usingModule = false; 126 Module databindModule = IBindingContext.class.getModule(); 127 if (databindModule != null) { 128 ModuleDescriptor descriptor = databindModule.getDescriptor(); 129 if (descriptor != null) { 130 // add the databind module to the task 131 compiler.addRootModule(ObjectUtils.notNull(descriptor.name())); 132 usingModule = true; 133 } 134 } 135 136 String classPath = System.getProperty("java.class.path"); 137 String modulePath = System.getProperty("jdk.module.path"); 138 if (usingModule) { 139 // use classpath and modulepath from the JDK 140 if (classPath != null) { 141 Arrays.stream(classPath.split(":")).forEachOrdered(compiler::addToClassPath); 142 } 143 144 if (modulePath != null) { 145 Arrays.stream(modulePath.split(":")).forEachOrdered(compiler::addToModulePath); 146 } 147 } else { 148 // use classpath only 149 if (classPath != null) { 150 Arrays.stream(classPath.split(":")).forEachOrdered(compiler::addToClassPath); 151 } 152 153 if (modulePath != null) { 154 Arrays.stream(modulePath.split(":")).forEachOrdered(compiler::addToClassPath); 155 } 156 } 157 158 JavaCompilerSupport.CompilationResult result = compiler.compile(classes, null); 159 160 if (!result.isSuccessful()) { 161 DiagnosticCollector<?> diagnostics = new DiagnosticCollector<>(); 162 if (LOGGER.isErrorEnabled()) { 163 LOGGER.error(diagnostics.getDiagnostics().toString()); 164 } 165 throw new IllegalStateException(String.format("failed to compile classes: %s%nClasspath: %s%nModule Path: %s%n%s", 166 classesToCompile.stream() 167 .map(clazz -> clazz.getClassName().canonicalName()) 168 .collect(Collectors.joining(",")), 169 diagnostics.getDiagnostics().toString(), 170 compiler.getClassPath().stream() 171 .collect(Collectors.joining(":")), 172 compiler.getModulePath().stream() 173 .collect(Collectors.joining(":")))); 174 } 175 return production; 176 } 177}