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

io.helidon.build.maven.cache.ConfigDiff Maven / Gradle / Ivy

There is a newer version: 4.0.16
Show newest version
/*
 * Copyright (c) 2021, 2024 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.maven.cache;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.helidon.build.common.Diff;
import io.helidon.build.common.xml.XMLElement;

import static io.helidon.build.maven.cache.ConfigHelper.subpath;

/**
 * Config diff.
 */
public abstract class ConfigDiff {

    private ConfigDiff() {
    }

    /**
     * Get the diff description.
     *
     * @return diff description
     */
    public abstract String asString();

    /**
     * Diff two XML elements.
     *
     * @param orig   orig element
     * @param actual actual element
     * @return list
     */
    static List diff(XMLElement orig, XMLElement actual) {
        return diff("", orig, actual);
    }

    private static List diff(String path, XMLElement orig, XMLElement actual) {
        List configDiffs = new ArrayList<>();

        // diff value
        String origValue = orig.value();
        String actualValue = actual.value();
        if (!origValue.equals(actualValue)) {
            configDiffs.add(new Update(path, origValue, actualValue));
        }

        // diff attributes
        Map origAttrs = orig.attributes();
        Map actualAttrs = actual.attributes();
        origAttrs.forEach((attrName, origAttrValue) -> {
            String actualAttrValue = actualAttrs.get(attrName);
            String attrPath = (path.isEmpty() ? "" : (path + "/")) + "@" + attrName;
            if (actualAttrValue == null) {
                configDiffs.add(new Remove(attrPath));
            } else if (!origAttrValue.equals(actualAttrValue)) {
                configDiffs.add(new Update(attrPath, origAttrValue, actualAttrValue));
            }
        });
        actualAttrs.forEach((attrName, actualAttrValue) -> {
            String attrPath = (path.isEmpty() ? "" : (path + "/")) + "@" + attrName;
            if (!origAttrs.containsKey(attrName)) {
                configDiffs.add(new Add(attrPath));
            }
        });

        List> rawDiffs = Diff.diff(orig.children(), actual.children());

        // find ADD and REMOVE with the same paths
        // Add config diff for MOVE with different orig and actual paths
        Map>> diffsByPath = new HashMap<>();
        for (Diff diff : rawDiffs) {
            if (diff.isAdd()) {
                String p = subpath(actual, diff.element());
                diffsByPath.computeIfAbsent(p, k -> new ArrayList<>()).add(diff);
            } else if (diff.isRemove()) {
                String p = subpath(orig, diff.element());
                diffsByPath.computeIfAbsent(p, k -> new ArrayList<>()).add(diff);
            } else {
                // Move
                String actualPath = subpath(actual, diff.element());
                String origPath = subpath(orig, diff.element());
                if (!actualPath.equals(origPath)) {
                    configDiffs.add(new Move(origPath, actualPath));
                }
            }
        }

        diffsByPath.forEach((p, diffs) -> {
            if (diffs.size() == 1) {
                Diff diff = diffs.get(0);
                if (diff.isAdd()) {
                    configDiffs.add(new Add(p));
                    return;
                }
                if (diff.isRemove()) {
                    configDiffs.add(new Remove(p));
                    return;
                }
            } else if (diffs.size() == 2) {
                Diff diff1 = diffs.get(0);
                Diff diff2 = diffs.get(1);
                if (diff1.isAdd() && diff2.isRemove() || diff1.isRemove() && diff2.isAdd()) {
                    // decompose ADD and REMOVE with the same path
                    // I.e. diff ADDED and REMOVED elements for fine-grained details
                    XMLElement origElt = diff1.isRemove() ? diff1.element() : diff2.element();
                    XMLElement actualElt = diff1.isAdd() ? diff1.element() : diff2.element();
                    configDiffs.addAll(diff(p, origElt, actualElt));
                    return;
                }
            }
            throw new IllegalStateException(String.format(
                    "Invalid diffs for path: %s - %s", p, diffs));
        });
        return configDiffs;
    }

    /**
     * Add.
     */
    static class Add extends ConfigDiff {

        private final String path;

        Add(String path) {
            this.path = path;
        }

        @Override
        public String asString() {
            return path + " has been added";
        }
    }

    /**
     * Remove.
     */
    static class Remove extends ConfigDiff {

        private final String path;

        Remove(String path) {
            this.path = path;
        }

        @Override
        public String asString() {
            return path + " has been removed";
        }
    }

    /**
     * Update.
     */
    static class Update extends ConfigDiff {

        private final String path;
        private final String origValue;
        private final String actualValue;

        Update(String path, Object origValue, Object actualValue) {
            this.path = path;
            this.origValue = origValue.toString();
            this.actualValue = actualValue.toString();
        }

        String orig() {
            return origValue;
        }

        String actual() {
            return actualValue;
        }

        @Override
        public String asString() {
            return path + " was '" + origValue + "' but is now '" + actualValue + "'";
        }
    }

    /**
     * Move.
     */
    static class Move extends ConfigDiff {

        private final String origPath;
        private final String actualPath;

        Move(String origPath, String actualPath) {
            this.origPath = origPath;
            this.actualPath = actualPath;
        }

        @Override
        public String asString() {
            return origPath + " has been moved to " + actualPath;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy