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

io.helidon.build.cache.ProjectState Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 Oracle and/or its affiliates.
 *
 * 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.
 */
package io.helidon.build.cache;

import java.io.BufferedReader;
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.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.Xpp3DomWriter;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Project state.
 */
final class ProjectState {

    static final String STATE_FILE_NAME = "state.xml";

    private final Properties properties;
    private final ArtifactEntry artifact;
    private final List attachedArtifacts;
    private final List compileSourceRoots;
    private final List testCompileSourceRoots;
    private final ProjectFiles projectFiles;
    private final List executions;
    private final Map> executionMatches;

    private ProjectState(Properties properties,
                         ArtifactEntry artifact,
                         List attachedArtifacts,
                         List compileSourceRoots,
                         List testCompileSourceRoots,
                         ProjectFiles projectFiles,
                         List executions) {

        this.properties = Objects.requireNonNull(properties, "properties is null");
        this.artifact = artifact;
        this.attachedArtifacts = attachedArtifacts == null ? List.of() : attachedArtifacts;
        this.compileSourceRoots = compileSourceRoots == null ? List.of() : compileSourceRoots;
        this.testCompileSourceRoots = testCompileSourceRoots == null ? List.of() : testCompileSourceRoots;
        this.projectFiles = Objects.requireNonNull(projectFiles, "projectFiles is null");
        this.executions = executions == null ? List.of() : executions;
        this.executionMatches = new HashMap<>();
    }

    /**
     * Get the project files.
     *
     * @return project files, never {@code null}
     */
    ProjectFiles projectFiles() {
        return projectFiles;
    }

    /**
     * Load the project state from file.
     *
     * @param project maven project
     * @return state if state file exists, or {@code null}
     * @throws IOException            if an IO error occurs
     * @throws XmlPullParserException if a parsing error occurs
     */
    static ProjectState load(MavenProject project) throws IOException, XmlPullParserException {
        Path stateFile = project.getModel().getProjectDirectory().toPath()
                                .resolve(project.getModel().getBuild().getDirectory())
                                .resolve(STATE_FILE_NAME);
        if (!Files.exists(stateFile)) {
            return null;
        }
        BufferedReader reader = Files.newBufferedReader(stateFile);
        Xpp3Dom rootElt = Xpp3DomBuilder.build(reader, false);
        Properties properties = new Properties();
        Xpp3Dom propertiesElt = rootElt.getChild("properties");
        if (propertiesElt != null) {
            for (Xpp3Dom propertyElt : propertiesElt.getChildren("property")) {
                String name = propertyElt.getAttribute("name");
                String value = propertyElt.getAttribute("value");
                if (name != null && !name.isEmpty() && value != null) {
                    properties.setProperty(name, value);
                }
            }
        }
        ArtifactEntry artifact = null;
        Xpp3Dom artifactElt = rootElt.getChild(ArtifactEntry.XML_ELEMENT_NAME);
        if (artifactElt != null) {
            artifact = ArtifactEntry.fromXml(artifactElt);
        }
        List attachedArtifacts = new LinkedList<>();
        Xpp3Dom attachedArtifactsElt = rootElt.getChild("attached-artifacts");
        if (attachedArtifactsElt != null) {
            for (Xpp3Dom attachedArtifactElt : attachedArtifactsElt.getChildren(ArtifactEntry.XML_ELEMENT_NAME)) {
                attachedArtifacts.add(ArtifactEntry.fromXml(attachedArtifactElt));
            }
        }
        List compileSourceRoots = new LinkedList<>();
        Xpp3Dom compileSourceRootsElt = rootElt.getChild("compile-source-roots");
        if (compileSourceRootsElt != null) {
            for (Xpp3Dom pathElt : compileSourceRootsElt.getChildren("path")) {
                String path = pathElt.getValue();
                if (path != null && !path.isEmpty()) {
                    compileSourceRoots.add(path);
                }
            }
        }
        List testCompileSourceRoots = new LinkedList<>();
        Xpp3Dom testCompileSourceRootsElt = rootElt.getChild("test-compile-source-roots");
        if (testCompileSourceRootsElt != null) {
            for (Xpp3Dom pathElt : testCompileSourceRootsElt.getChildren("path")) {
                String path = pathElt.getValue();
                if (path != null && !path.isEmpty()) {
                    testCompileSourceRoots.add(path);
                }
            }
        }
        ProjectFiles projectFiles = ProjectFiles.fromXml(rootElt.getChild(ProjectFiles.XML_ELEMENT_NAME));
        List executions = new LinkedList<>();
        Xpp3Dom executionsElt = rootElt.getChild("executions");
        if (executionsElt != null) {
            for (Xpp3Dom executionElt : executionsElt.getChildren(ExecutionEntry.XML_ELEMENT_NAME)) {
                executions.add(ExecutionEntry.fromXml(executionElt));
            }
        }
        return new ProjectState(properties, artifact, attachedArtifacts, compileSourceRoots, testCompileSourceRoots,
                projectFiles, executions);
    }

    /**
     * Save the project state.
     *
     * @param project Maven project
     * @throws IOException if an IO error occurs
     */
    void save(MavenProject project) throws IOException {
        Path buildDir = project.getModel().getProjectDirectory().toPath()
                               .resolve(project.getModel().getBuild().getDirectory());
        Xpp3Dom rootElt = new Xpp3Dom("project-state");
        Xpp3Dom propertiesElt = new Xpp3Dom("properties");
        for (String propName : properties.stringPropertyNames()) {
            Xpp3Dom propertyElt = new Xpp3Dom("property");
            propertyElt.setAttribute("name", propName);
            propertyElt.setAttribute("value", properties.getProperty(propName));
            propertiesElt.addChild(propertyElt);
        }
        rootElt.addChild(propertiesElt);
        if (artifact != null) {
            rootElt.addChild(artifact.toXml());
        }

        Xpp3Dom attachedArtifactsElt = new Xpp3Dom("attached-artifacts");
        for (ArtifactEntry artifact : attachedArtifacts) {
            attachedArtifactsElt.addChild(artifact.toXml());
        }
        rootElt.addChild(attachedArtifactsElt);
        Xpp3Dom compileSourceRootsElt = new Xpp3Dom("compile-source-roots");
        for (String path : compileSourceRoots) {
            Xpp3Dom pathElt = new Xpp3Dom("path");
            pathElt.setValue(path);
            compileSourceRootsElt.addChild(pathElt);
        }
        rootElt.addChild(compileSourceRootsElt);
        Xpp3Dom testCompileSourceRootsElt = new Xpp3Dom("test-compile-source-roots");
        for (String path : testCompileSourceRoots) {
            Xpp3Dom pathElt = new Xpp3Dom("path");
            pathElt.setValue(path);
            testCompileSourceRootsElt.addChild(pathElt);
        }
        rootElt.addChild(testCompileSourceRootsElt);
        rootElt.addChild(projectFiles.toXml());
        Xpp3Dom executionsElt = new Xpp3Dom("executions");
        for (ExecutionEntry execution : executions) {
            executionsElt.addChild(execution.toXml());
        }
        rootElt.addChild(executionsElt);
        if (!Files.exists(buildDir)) {
            Files.createDirectories(buildDir);
        }
        FileWriter writer = new FileWriter(buildDir.resolve(STATE_FILE_NAME).toFile());
        Xpp3DomWriter.write(writer, rootElt);
        writer.flush();
        writer.close();
    }

    /**
     * Apply this state to the given project.
     *
     * @param project Maven project
     * @param session Maven session
     */
    @SuppressWarnings("unchecked")
    void apply(MavenProject project, MavenSession session) {
        Path projectDir = project.getModel().getProjectDirectory().toPath();
        properties.forEach((k, v) -> project.getProperties().put(k, loadPropValue(session, (String) v)));
        Optional.ofNullable(artifact)
                .map(a -> a.toArtifact(project))
                .ifPresent(project::setArtifact);
        compileSourceRoots
                .stream()
                .map(projectDir::resolve)
                .map(Object::toString)
                .filter(project.getCompileSourceRoots()::contains)
                .forEach(project::addCompileSourceRoot);
        testCompileSourceRoots
                .stream()
                .map(projectDir::resolve)
                .map(Object::toString)
                .filter(project.getTestCompileSourceRoots()::contains)
                .forEach(project::addTestCompileSourceRoot);
        attachedArtifacts
                .stream()
                .map(a -> a.toArtifact(project))
                .forEach(project::addAttachedArtifact);
    }

    /**
     * Test if the given execution matches any of the ones in this state using
     * {@link ExecutionEntry#matches(ExecutionEntry)}.
     *
     * @param execution execution to match
     * @return {@code true} if found, {@code false} otherwise
     */
    boolean hasMatchingExecution(ExecutionEntry execution) {
        return findMatchingExecution(execution) != null;
    }

    /**
     * Find a recorded execution matching the given one using
     * {@link ExecutionEntry#matches(ExecutionEntry)}.
     *
     * @param execution execution to match
     * @return ExecutionEntry or {@code null} if not found
     */
    ExecutionEntry findMatchingExecution(ExecutionEntry execution) {
        return executionMatches.computeIfAbsent(execution, (key) -> {
            for (ExecutionEntry exec : executions) {
                if (exec.matches(key)) {
                    return Optional.of(exec);
                }
            }
            return Optional.empty();
        }).orElse(null);
    }

    /**
     * Create a state for the given project and merge it with the existing state for this project.
     *
     * @param state           existing state, may be {@code null}
     * @param project         Maven project
     * @param session         Maven session
     * @param newExecutions   new executions
     * @param newProjectFiles current project files
     * @return ProjectState
     * @throws IOException if an IO error occurs while scanning project files
     */
    static ProjectState merge(ProjectState state,
                              MavenProject project,
                              MavenSession session,
                              List newExecutions,
                              ProjectFiles newProjectFiles)
            throws IOException {

        Path projectDir = project.getModel().getProjectDirectory().toPath();
        List executions;
        Properties properties;
        if (state == null) {
            executions = List.of();
            properties = new Properties();
        } else {
            executions = state.executions;
            properties = state.properties;
        }
        Properties projectProps = new Properties();
        project.getProperties().forEach((k, v) -> projectProps.put(k, savePropValue(session, (String) v)));
        return new ProjectState(
                mergeProperties(properties, projectProps),
                Optional.ofNullable(project.getArtifact())
                        .map(a -> ArtifactEntry.create(a, project))
                        .orElse(null),
                project.getAttachedArtifacts()
                       .stream()
                       .map(a -> ArtifactEntry.create(a, project))
                       .collect(Collectors.toList()),
                project.getCompileSourceRoots()
                       .stream()
                       .map(Paths::get)
                       .map(projectDir::relativize)
                       .map(Path::toString)
                       .collect(Collectors.toList()),
                project.getTestCompileSourceRoots()
                       .stream()
                       .map(Paths::get)
                       .map(projectDir::relativize)
                       .map(Path::toString)
                       .collect(Collectors.toList()),
                newProjectFiles == null ? ProjectFiles.of(project, session) : newProjectFiles,
                Stream.concat(executions.stream().filter(exec -> newExecutions.stream().noneMatch(exec::matches)),
                        newExecutions.stream()).collect(Collectors.toList()));
    }

    private static String loadPropValue(MavenSession session, String value) {
        return value.replace("#{exec.root.dir}", session.getExecutionRootDirectory());
    }

    private static String savePropValue(MavenSession session, String value) {
        return value.replace(session.getExecutionRootDirectory(), "#{exec.root.dir}");
    }

    private static Properties mergeProperties(Properties props1, Properties props2) {
        Properties properties = new Properties();
        props1.forEach(properties::put);
        props2.forEach(properties::put);
        return properties;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy