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

com.regnosys.rosetta.common.compile.JavaCSourceCancellableCompiler Maven / Gradle / Ivy

Go to download

Rune Common is a java library that is utilised by Rosetta Code Generators and models expressed in the Rosetta DSL.

There is a newer version: 11.27.1
Show newest version
package com.regnosys.rosetta.common.compile;

/*-
 * ==============
 * Rune Common
 * ==============
 * Copyright (C) 2018 - 2024 REGnosys
 * ==============
 * 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
 * 
 *      http://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.
 * ==============
 */

import com.google.common.base.StandardSystemProperty;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class JavaCSourceCancellableCompiler implements JavaCancellableCompiler {
    private static final Logger LOGGER = LoggerFactory.getLogger(JavaCSourceCancellableCompiler.class);
    public static final int DEFAULT_THREAD_POLL_INTERVAL_MS = 100;
    public static final int DEFAULT_MAX_COMPILE_TIMEOUT_MS = 300_000;

    private final int threadPollIntervalMs;
    private final int maxCompileTimeoutMs;
    private final ExecutorService executorService;
    private final boolean useSystemClassPath;
    private final boolean deleteOnError;
    private final boolean isVerbose;
    private final JavaCompileReleaseFlag releaseFlag;
    private final Path[] additionalClassPaths;

    public JavaCSourceCancellableCompiler(int threadPollIntervalMs,
                                          int maxCompileTimeoutMs,
                                          ExecutorService executorService,
                                          boolean useSystemClassPath,
                                          boolean deleteOnError,
                                          boolean isVerbose,
                                          JavaCompileReleaseFlag releaseFlag,
                                          Path... additionalClassPaths) {
        this.threadPollIntervalMs = threadPollIntervalMs;
        this.maxCompileTimeoutMs = maxCompileTimeoutMs;
        this.executorService = executorService;
        this.useSystemClassPath = useSystemClassPath;
        this.isVerbose = isVerbose;
        this.deleteOnError = deleteOnError;
        this.releaseFlag = releaseFlag;
        this.additionalClassPaths = additionalClassPaths;
    }

    public JavaCSourceCancellableCompiler(ExecutorService executorService,
                                          boolean useSystemClassPath,
                                          boolean deleteOnError,
                                          boolean isVerbose,
                                          JavaCompileReleaseFlag releaseFlag,
                                          Path... additionalClassPaths) {
        this(DEFAULT_THREAD_POLL_INTERVAL_MS,
                DEFAULT_MAX_COMPILE_TIMEOUT_MS,
                executorService,
                useSystemClassPath,
                deleteOnError,
                isVerbose,
                releaseFlag,
                additionalClassPaths);
    }

    @Override
    public JavaCompilationResult compile(List sourceJavaPaths,
                                         Path targetPath,
                                         CancelIndicator cancelIndicator) throws ExecutionException, InterruptedException, TimeoutException {
        DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>();
        JavaCompiler.CompilationTask compilationTask = getCompilationTask(sourceJavaPaths, targetPath, diagnosticCollector);

        Future submittedTask = executorService.submit(compilationTask);

        CompilationCompletionState compilationCompletionState = submitAndWait(submittedTask, cancelIndicator, targetPath);

        if (deleteOnError && compilationCompletionState == CompilationCompletionState.COMPILATION_FAILURES) {
            wipeTargetPath(targetPath);
        }

        return new JavaCompilationResult(compilationCompletionState,
                diagnosticCollector.getDiagnostics());
    }

    private void wipeTargetPath(Path targetPath) {
        try {
            FileUtils.cleanDirectory(targetPath.toFile());
        } catch (IOException e) {
            throw new CompilationTargetDeletionException("Failed to delete target classes after compilation error", e);
        }
    }

    private CompilationCompletionState submitAndWait(Future submittedTask, CancelIndicator cancelIndicator, Path targetPath) throws InterruptedException, ExecutionException, TimeoutException {
        int maxWaitCycles = maxCompileTimeoutMs / threadPollIntervalMs;

        for (int i = 0; i < maxWaitCycles; i++) {
            try {
                return submittedTask.get(threadPollIntervalMs, TimeUnit.MILLISECONDS) ?
                        CompilationCompletionState.COMPILATION_SUCCESS : CompilationCompletionState.COMPILATION_FAILURES;
            } catch (TimeoutException e) {
                if (cancelIndicator.isCancelled()) {
                    boolean cancellationAttemptedSuccessfully = submittedTask.cancel(true);
                    if (!cancellationAttemptedSuccessfully && submittedTask.isCancelled()) {
                        LOGGER.info("Attempted to cancel a compilation task but the cancellation attempt was unsuccessful, this may be because the task was already cancelled");
                    }
                    if (!submittedTask.isCancelled()) {
                        String message = String.format(
                        "Failed to cancel compile task while writing to %s. This may mean that the compilation process runs long or hangs.",
                                targetPath);
                        LOGGER.error(message);
                        throw new CompilationCancellationException(message);
                    }
                    return CompilationCompletionState.NOT_COMPLETE;
                }
                LOGGER.trace("Timed out whilst getting from task, iteration {}", i);
            }
        }
        throw new TimeoutException("Timed out waiting for compilation task");
    }


    private JavaCompiler.CompilationTask getCompilationTask(List sourceJavaPaths,
                                                            Path targetPath,
                                                            DiagnosticCollector diagnosticCollector) {
        List compilerArguments = createCompilerArguments(targetPath);
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnosticCollector, null, null);
        List sourceFiles = sourceJavaPaths.stream().map(Path::toFile).collect(Collectors.toList());
        Iterable javaFileObjects = fileManager.getJavaFileObjectsFromFiles(sourceFiles);
        return compiler.getTask(null, fileManager, diagnosticCollector, compilerArguments, null, javaFileObjects);
    }

    private List createCompilerArguments(Path targetPath) {
        List args = new ArrayList<>();

        if (isVerbose) {
            args.add("-verbose");
        } else {
            args.add("-nowarn");
            args.add("-g:none");
        }

        args.add("-d");
        args.add(targetPath.toAbsolutePath().toString());

        args.add("-classpath");
        args.add(createClasspath(targetPath, useSystemClassPath, additionalClassPaths));

        args.add("-encoding");
        args.add("UTF-8");
        args.add("-proc:none");
        args.add("--release");
        args.add(releaseFlag.getVersion());

        return args;
    }

    private String createClasspath(Path targetPath, boolean useSystemClassPath, Path... additionalClassPaths) {
        StringBuilder classpath = new StringBuilder();

        classpath.append(targetPath.toAbsolutePath());
        classpath.append(File.pathSeparator);

        if (useSystemClassPath) {
            String javaClassPath = StandardSystemProperty.JAVA_CLASS_PATH.value();
            if (javaClassPath != null && !javaClassPath.isEmpty()) {
                classpath.append(javaClassPath);
                classpath.append(File.pathSeparator);
            } else {
                LOGGER.warn("Compile called with useSystemClassPath flag set but the system classpath is empty, continuing compilation without");
            }
        }

        String additionalClassPathsString = Arrays.stream(additionalClassPaths)
                .map(Path::toAbsolutePath)
                .map(Path::toString)
                .collect(Collectors.joining(File.pathSeparator));

        classpath.append(additionalClassPathsString);

        return classpath.toString();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy