All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fluxtion.agrona.generation.CompilerUtil Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2024 Real Logic Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fluxtion.agrona.generation;

import com.fluxtion.agrona.LangUtil;
import com.fluxtion.agrona.SystemUtil;

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

import static java.util.stream.Collectors.toList;

/**
 * Utilities for compiling Java source files at runtime.
 */
public final class CompilerUtil
{
    /**
     * Temporary directory for files.
     */
    private static final String TEMP_DIR_NAME = SystemUtil.tmpDirName();

    private CompilerUtil()
    {
    }

    /**
     * Compile a {@link Map} of source files in-memory resulting in a {@link Class} which is named.
     *
     * @param className to return after compilation.
     * @param sources   to be compiled.
     * @return the named class that is the result of the compilation.
     * @throws ClassNotFoundException of the named class cannot be found.
     */
    public static Class compileInMemory(final String className, final Map sources)
        throws ClassNotFoundException
    {
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (null == compiler)
        {
            throw new IllegalStateException("JDK required to run tests. JRE is not sufficient.");
        }

        final JavaFileManager fileManager = new ClassFileManager<>(
            compiler.getStandardFileManager(null, null, null));
        final DiagnosticCollector diagnostics = new DiagnosticCollector<>();
        final JavaCompiler.CompilationTask task = compiler.getTask(
            null, fileManager, diagnostics, null, null, wrap(sources));

        return compileAndLoad(className, diagnostics, fileManager, task);
    }

    /**
     * Compile a {@link Map} of source files on disk resulting in a {@link Class} which is named.
     *
     * @param className to return after compilation.
     * @param sources   to be compiled.
     * @return the named class that is the result of the compilation.
     * @throws ClassNotFoundException of the named class cannot be found.
     * @throws IOException            if an error occurs when writing to disk.
     */
    public static Class compileOnDisk(final String className, final Map sources)
        throws ClassNotFoundException, IOException
    {
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (null == compiler)
        {
            throw new IllegalStateException("JDK required to run tests. JRE is not sufficient.");
        }

        final DiagnosticCollector diagnostics = new DiagnosticCollector<>();
        try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null))
        {
            final ArrayList options = new ArrayList<>(Arrays.asList(
                "-classpath", System.getProperty("java.class.path") + File.pathSeparator + TEMP_DIR_NAME));

            final Collection files = persist(sources);
            final Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(files);
            final JavaCompiler.CompilationTask task = compiler.getTask(
                null, fileManager, diagnostics, options, null, compilationUnits);

            return compileAndLoad(className, diagnostics, fileManager, task);
        }
    }

    /**
     * Compile and load a class.
     *
     * @param className   name of the class to compile.
     * @param diagnostics attached to the compilation task.
     * @param fileManager to load compiled class from disk.
     * @param task        compilation task.
     * @return {@link Class} for the compiled class or {@code null} if compilation fails.
     * @throws ClassNotFoundException if compiled class was not loaded.
     */
    public static Class compileAndLoad(
        final String className,
        final DiagnosticCollector diagnostics,
        final JavaFileManager fileManager,
        final JavaCompiler.CompilationTask task) throws ClassNotFoundException
    {
        if (!compile(diagnostics, task))
        {
            return null;
        }

        return fileManager.getClassLoader(null).loadClass(className);
    }

    /**
     * Execute compilation task and report errors if it fails.
     *
     * @param diagnostics attached to the compilation task.
     * @param task        compilation to be executed.
     * @return {@code true} if compilation succeeds.
     */
    public static boolean compile(
        final DiagnosticCollector diagnostics, final JavaCompiler.CompilationTask task)
    {
        final Boolean succeeded = task.call();

        if (!succeeded)
        {
            for (final Diagnostic diagnostic : diagnostics.getDiagnostics())
            {
                System.err.println(diagnostic.getCode());
                System.err.println(diagnostic.getKind());

                final JavaFileObject source = diagnostic.getSource();
                System.err.printf("Line = %d, Col = %d, File = %s",
                    diagnostic.getLineNumber(), diagnostic.getColumnNumber(), source);

                System.err.println("Start: " + diagnostic.getStartPosition());
                System.err.println("End: " + diagnostic.getEndPosition());
                System.err.println("Pos: " + diagnostic.getPosition());

                try
                {
                    final String content = source.getCharContent(true).toString();
                    final int begin = content.lastIndexOf('\n', (int)diagnostic.getStartPosition());
                    final int end = content.indexOf('\n', (int)diagnostic.getEndPosition());
                    System.err.println(diagnostic.getMessage(null));
                    System.err.println(content.substring(Math.max(0, begin), end));
                }
                catch (final IOException ex)
                {
                    LangUtil.rethrowUnchecked(ex);
                }
            }
        }

        return succeeded;
    }

    /**
     * Persist source files to disc.
     *
     * @param sources to persist.
     * @return a collection of {@link File} objects pointing to the persisted sources.
     * @throws IOException in case of I/O errors.
     */
    public static Collection persist(final Map sources) throws IOException
    {
        final Collection files = new ArrayList<>(sources.size());
        for (final Map.Entry entry : sources.entrySet())
        {
            final String fqClassName = entry.getKey();
            String className = fqClassName;
            Path path = Paths.get(TEMP_DIR_NAME);

            final int indexOfLastDot = fqClassName.lastIndexOf('.');
            if (indexOfLastDot != -1)
            {
                className = fqClassName.substring(indexOfLastDot + 1);

                path = Paths.get(
                    TEMP_DIR_NAME + fqClassName.substring(0, indexOfLastDot).replace('.', File.separatorChar));
                Files.createDirectories(path);
            }

            final File file = new File(path.toString(), className + ".java");
            files.add(file);

            try (FileWriter out = new FileWriter(file))
            {
                out.append(entry.getValue());
                out.flush();
            }
        }

        return files;
    }

    private static Collection wrap(final Map sources)
    {
        return sources
            .entrySet()
            .stream()
            .map((e) -> new CharSequenceJavaFileObject(e.getKey(), e.getValue()))
            .collect(toList());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy