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}