1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.codegen;
7   
8   import gov.nist.secauto.metaschema.core.model.IModule;
9   import gov.nist.secauto.metaschema.core.util.ObjectUtils;
10  import gov.nist.secauto.metaschema.databind.IBindingContext;
11  import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration;
12  import gov.nist.secauto.metaschema.databind.codegen.config.IBindingConfiguration;
13  
14  import org.apache.logging.log4j.LogManager;
15  import org.apache.logging.log4j.Logger;
16  import org.eclipse.jdt.annotation.Owning;
17  
18  import java.io.IOException;
19  import java.lang.module.ModuleDescriptor;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.net.URLClassLoader;
23  import java.nio.file.Path;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  import java.util.Arrays;
27  import java.util.List;
28  import java.util.stream.Collectors;
29  
30  import javax.tools.DiagnosticCollector;
31  
32  import edu.umd.cs.findbugs.annotations.NonNull;
33  
34  /**
35   * This class provides methods to generate and dynamically compile Java code
36   * based on a Module. The {@link #newClassLoader(Path, ClassLoader)} method can
37   * be used to get a {@link ClassLoader} for Java code previously generated by
38   * this class.
39   */
40  public final class ModuleCompilerHelper {
41    private static final Logger LOGGER = LogManager.getLogger(ModuleCompilerHelper.class);
42  
43    private ModuleCompilerHelper() {
44      // disable construction
45    }
46  
47    /**
48     * Create a new classloader capable of loading Java classes generated in the
49     * provided {@code classDir}.
50     *
51     * @param classDir
52     *          the directory where generated Java classes have been compiled
53     * @param parent
54     *          the classloader to delegate to when the created class loader cannot
55     *          load a class
56     * @return the new class loader
57     */
58    @SuppressWarnings("resource")
59    @Owning
60    @NonNull
61    public static ClassLoader newClassLoader(
62        @NonNull final Path classDir,
63        @NonNull final ClassLoader parent) {
64      return ObjectUtils.notNull(AccessController.doPrivileged(
65          (PrivilegedAction<ClassLoader>) () -> {
66            try {
67              return new URLClassLoader(new URL[] { classDir.toUri().toURL() }, parent);
68            } catch (MalformedURLException ex) {
69              throw new IllegalStateException("unable to configure class loader", ex);
70            }
71          }));
72    }
73  
74    /**
75     * Generate and compile Java class, representing the provided Module
76     * {@code module} and its related definitions, using the default binding
77     * configuration.
78     *
79     * @param module
80     *          the Module module to generate Java classes for
81     * @param classDir
82     *          the directory to generate the classes in
83     * @return information about the generated classes
84     * @throws IOException
85     *           if an error occurred while generating or compiling the classes
86     */
87    @NonNull
88    public static IProduction compileMetaschema(
89        @NonNull IModule module,
90        @NonNull Path classDir)
91        throws IOException {
92      return compileModule(module, classDir, new DefaultBindingConfiguration());
93    }
94  
95    /**
96     * Generate and compile Java class, representing the provided Module
97     * {@code module} and its related definitions, using the provided custom
98     * {@code bindingConfiguration}.
99     *
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 }