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

io.quarkiverse.groovy.deployment.GroovyCompilationProvider Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */
package io.quarkiverse.groovy.deployment;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.tools.GroovyClass;
import org.jboss.logging.Logger;

import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovySystem;
import io.quarkus.deployment.dev.CompilationProvider;
import io.quarkus.paths.PathCollection;

/**
 * {@code GroovyCompilationProvider} compiles sources written in Groovy that are modified in dev mode.
 */
public class GroovyCompilationProvider implements CompilationProvider {

    private static final Logger log = Logger.getLogger(GroovyCompilationProvider.class);
    private static final String GROOVY_PROVIDER_KEY = "groovy";
    private static final Pattern OPTION_PATTERN = Pattern.compile("([^=]+)=(.*)");

    @Override
    public String getProviderKey() {
        return GROOVY_PROVIDER_KEY;
    }

    @Override
    public Set handledExtensions() {
        return Set.of(".groovy");
    }

    @Override
    public void compile(Set filesToCompile, Context context) {
        CompilerConfiguration cc = getCompilerConfiguration(context);
        cc.setSourceEncoding(context.getSourceEncoding().name());
        cc.setTargetBytecode(context.getTargetJvmVersion());
        cc.setTargetDirectory(context.getOutputDirectory().getAbsolutePath());
        try (URLClassLoader parent = createNewClassLoader(
                Stream.of(Set.of(cc.getTargetDirectory()), context.getClasspath(), context.getReloadableClasspath())
                        .flatMap(Collection::stream)
                        .collect(Collectors.toList()));
                GroovyClassLoader groovyClassLoader = new GroovyClassLoader(parent, cc);
                GroovyClassLoader transformLoader = new GroovyClassLoader(parent)) {
            CompilationUnit unit = new CompilationUnit(cc, null, groovyClassLoader, transformLoader);
            filesToCompile.forEach(unit::addSource);
            unit.compile();
            // log compiled classes
            List classes = unit.getClasses();
            log.infof("Compiled %d file%s.", classes.size(), classes.size() > 1 ? "s" : "");
        } catch (CompilationFailedException e) {
            // Convert the CompilationFailedException into a RuntimeException to prevent serialization issues in remote
            // dev mode
            throw new RuntimeException(e.getMessage());
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    /**
     * Extracts from the given context the compiler options, and if they exist, it provides an instance of
     * {@code CompilerConfiguration} with the options applied, an empty {@code CompilerConfiguration} otherwise.
     *
     * @param context the context from which the compiler options are extracted.
     * @return an {@code CompilerConfiguration} corresponding to the given context.
     */
    private static CompilerConfiguration getCompilerConfiguration(Context context) {
        final Collection compilerOptions = context.getCompilerOptions(GROOVY_PROVIDER_KEY);
        CompilerConfiguration cc;
        if (compilerOptions != null && !compilerOptions.isEmpty()) {
            Properties properties = new Properties(compilerOptions.size());
            for (String rawOption : compilerOptions) {
                final Matcher matcher = OPTION_PATTERN.matcher(rawOption);
                if (!matcher.matches()) {
                    log.warnf("Groovy compiler option %s is invalid", rawOption);
                }
                properties.setProperty(matcher.group(1), matcher.group(2));
            }
            cc = new CompilerConfiguration(properties);
        } else {
            cc = new CompilerConfiguration();
        }
        return cc;
    }

    /**
     * @param classpath the compilation classpath from which the {@code URLClassLoader} is built.
     * @return an {@code URLClassLoader} with the System ClassLoader as parent and the classpath as URLs from which to
     *         load classes and resources.
     * @throws MalformedURLException if one {@code File} from the classpath cannot be converted into an {@code URL}.
     */
    private static URLClassLoader createNewClassLoader(final List classpath) throws MalformedURLException {
        List urlsList = new ArrayList<>();
        for (File file : classpath) {
            urlsList.add(file.toURI().toURL());
        }
        return new URLClassLoader(urlsList.toArray(new URL[0]), GroovySystem.class.getClassLoader());
    }

    @Override
    public Path getSourcePath(Path classFilePath, PathCollection sourcePaths, String classesPath) {
        // return same class so it is not removed
        return classFilePath;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy