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

io.helidon.build.cache.ConfigDiffs 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.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Config node diffs.
 */
final class ConfigDiffs implements Iterator {

    private final ConfigNode orig;
    private final ConfigNode actual;
    private final LinkedList stack;
    private boolean used;
    private Diff next;

    /**
     * Create a new config diffs.
     *
     * @param orig   original node, must be non {@code null}
     * @param actual actual node, must be non {@code null}
     */
    ConfigDiffs(ConfigNode orig, ConfigNode actual) {
        if (orig == null || actual == null) {
            throw new IllegalArgumentException();
        }
        this.orig = orig;
        this.actual = actual;
        stack = new LinkedList<>();
        stack.add(new NodeItem(orig, actual));
    }

    /**
     * Rewind the diff to iterate again.
     */
    ConfigDiffs rewind() {
        if (used) {
            stack.clear();
            stack.add(new NodeItem(orig, actual));
            used = false;
        }
        return this;
    }

    /**
     * Get the diffs as a {@link Stream}.
     *
     * @return stream of Diff
     */
    Stream stream() {
        return StreamSupport.stream(
                Spliterators.spliteratorUnknownSize(this, Spliterator.ORDERED), false);
    }

    @Override
    public Diff next() {
        if (next == null) {
            throw new NoSuchElementException();
        }
        used = true;
        Diff diff = next;
        next = null;
        return diff;
    }

    @Override
    public boolean hasNext() {
        while (next == null && !stack.isEmpty()) {
            Item item = stack.pop();
            if (item instanceof NodeItem) {
                NodeItem node = (NodeItem) item;
                next = item.toDiff();
                if (next == null) {
                    stack.addAll(0, node.attributes());
                    List children = node.children();
                    if (!children.isEmpty()) {
                        for (int i = children.size() - 1; i >= 0; i--) {
                            stack.push(children.get(i));
                        }
                    }
                }
            } else {
                next = item.toDiff();
            }
        }
        return next != null;
    }

    /**
     * Diff-able item.
     */
    private interface Item {

        /**
         * Create a {@link Diff} instance for this item.
         *
         * @return Diff
         */
        Diff toDiff();
    }

    private final class NodeItem implements Item {

        private final ConfigNode orig;
        private final ConfigNode actual;

        NodeItem(ConfigNode orig, ConfigNode actual) {
            if (orig == null && actual == null) {
                throw new IllegalArgumentException();
            }
            this.orig = orig;
            this.actual = actual;
        }

        List attributes() {
            Map origAttrs = orig.attributes();
            Map actualAttrs = actual.attributes();
            LinkedList attributes = new LinkedList<>();
            for (Map.Entry entry : origAttrs.entrySet()) {
                // exist in both
                // removed
                attributes.add(new AttributeItem(
                        this,
                        entry.getKey(),
                        entry.getValue(),
                        actualAttrs.getOrDefault(entry.getKey(), null)));
            }
            for (Map.Entry entry : actualAttrs.entrySet()) {
                if (!origAttrs.containsKey(entry.getKey())) {
                    // added
                    attributes.add(new AttributeItem(
                            this,
                            entry.getKey(),
                            null,
                            entry.getValue()));
                }
            }
            return attributes;
        }

        List children() {
            Iterator origIt = orig.children().iterator();
            Iterator actualIt = actual.children().iterator();
            List children = new LinkedList<>();
            while (origIt.hasNext() || actualIt.hasNext()) {
                ConfigNode origNext;
                ConfigNode actualNext;
                if (origIt.hasNext()) {
                    origNext = origIt.next();
                } else {
                    origNext = null;
                }
                if (actualIt.hasNext()) {
                    actualNext = actualIt.next();
                } else {
                    actualNext = null;
                }
                children.add(new NodeItem(origNext, actualNext));
            }
            return children;
        }

        @Override
        public Diff toDiff() {
            if (orig != null && actual == null) {
                return new Diff(orig, null, orig.path());
            }
            if (orig == null && actual != null) {
                return new Diff(null, actual, actual.path());
            }
            if (orig != null) {
                if (!orig.name().equals(actual.name())){
                    return new Diff(orig, actual, orig.path());
                }
                String origValue = orig.value();
                String actualValue = actual.value();
                if ((origValue != null && actualValue == null && !actual.hasChildren())
                        || (origValue == null && actualValue != null && !orig.hasChildren())
                        || (origValue != null && actualValue != null && !origValue.equals(actualValue))) {
                    return new Diff(origValue, actualValue, orig.path() + "/" + orig.name());
                }
            }
            return null;
        }
    }

    private final class AttributeItem implements Item {

        private final NodeItem node;
        private final String key;
        private final String origValue;
        private final String actualValue;

        AttributeItem(NodeItem node, String key, String origValue, String actualValue) {
            if (node == null || key == null || key.isEmpty()) {
                throw new IllegalArgumentException();
            }
            if (origValue == null && actualValue == null) {
                throw new IllegalArgumentException();
            }
            this.node = node;
            this.key = key;
            this.origValue = origValue;
            this.actualValue = actualValue;
        }

        private String path() {
            ConfigNode n;
            if (node.orig != null) {
                n = node.orig;
            } else {
                n = node.actual;
            }
            String path = n.path();
            if (n.parent() != null) {
                path += "/";
            }
            return path + n.name() + "{" + n.index() + "}" + "#" + key;
        }

        @Override
        public Diff toDiff() {
            if (!(origValue != null && origValue.equals(actualValue))) {
                return new Diff(origValue, actualValue, path());
            }
            return null;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy