aboutsummaryrefslogtreecommitdiffstats
path: root/config/src/main/java/com/yahoo/vespa/config/buildergen/LazyConfigCompiler.java
blob: 2af19d1c6f2c6d78c04c292a8b493982093bd078 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.config.buildergen;

import com.yahoo.config.ConfigInstance;

import javax.tools.*;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Locale;

/**
 * Represents a compiler that waits performing the compilation until the requested builder is requested from the
 * {@link CompiledBuilder}.
 *
 * @author Ulf Lilleengen
 */
public class LazyConfigCompiler implements ConfigCompiler {

    private final File outputDirectory;
    private final ClassLoader classLoader;
    private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    public LazyConfigCompiler(File outputDirectory) {
        this.outputDirectory = outputDirectory;
        try {
            this.classLoader = new URLClassLoader(new URL[]{outputDirectory.toURI().toURL()});
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Unable to create class loader for directory '" + outputDirectory.getAbsolutePath() + "'", e);
        }
    }

    @Override
    public CompiledBuilder compile(ConfigDefinitionClass defClass) {
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(new StringSourceObject(defClass.getName(), defClass.getDefinition()));
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, Locale.ENGLISH, null);
        Iterable<String> options = Arrays.asList("-d", outputDirectory.getAbsolutePath());
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);
        return new LazyCompiledBuilder(classLoader, defClass.getName(), new CompilationTask(task, diagnostics));
    }

    /**
     * Lazy implementation of compiled builder that defers compilation until class is requested.
     */
    private static class LazyCompiledBuilder implements CompiledBuilder {
        private final ClassLoader classLoader;
        private final String classUrl;
        private final CompilationTask compilationTask;
        private LazyCompiledBuilder(ClassLoader classLoader, String classUrl, CompilationTask compilationTask) {
            this.classLoader = classLoader;
            this.classUrl = classUrl;
            this.compilationTask = compilationTask;
        }

        @Override
        public <BUILDER extends ConfigInstance.Builder> BUILDER newInstance() {
            compileBuilder();
            String builderClassUrl = classUrl + "$Builder";
            return loadBuilder(builderClassUrl);

        }

        private void compileBuilder() {
            try {
                compilationTask.call();
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("Error compiling '" + classUrl + "'", e);
            }
        }

        @SuppressWarnings("unchecked")
        private <BUILDER extends ConfigInstance.Builder> BUILDER loadBuilder(String builderClassUrl) {
            try {
                Class<BUILDER> clazz = (Class<BUILDER>) classLoader.loadClass(builderClassUrl);
                return clazz.getDeclaredConstructor().newInstance();
            } catch (ReflectiveOperationException e) {
                throw new RuntimeException("Error creating new instance of '" + builderClassUrl + "'", e);
            }
        }
    }
}