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}