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

com.telenav.cactus.maven.refactoring.PomCategorizer Maven / Gradle / Ivy

Go to download

API for manipulating versions consistently across large trees of maven projects with an eye to ensuring the result is consistent and buildable.

There is a newer version: 1.5.49
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// © 2011-2022 Telenav, Inc.
//
// 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
//
// https://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 com.telenav.cactus.maven.refactoring;

import com.mastfrog.function.optional.ThrowingOptional;
import com.telenav.cactus.maven.model.MavenCoordinates;
import com.telenav.cactus.maven.model.ParentMavenCoordinates;
import com.telenav.cactus.maven.model.Pom;
import com.telenav.cactus.maven.model.resolver.Poms;
import com.telenav.cactus.scope.ProjectFamily;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static com.telenav.cactus.maven.refactoring.PomRole.*;
import static com.telenav.cactus.scope.ProjectFamily.familyOf;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;

/**
 * Infers roles for POMs based on their type, parent, whether they contain
 * modules and similar, in order to make update decisions based on their role in
 * the meta-project.
 *
 * @author Tim Boudreau
 */
public final class PomCategorizer
{
    private final Map> pomsForKind = new EnumMap<>(
            PomRole.class);
    private final Map> rolesForPom = new HashMap<>();
    private final Map> pomsForFamily = new HashMap<>();
    private final Map>> pomsForValueOfProperty = new HashMap<>();
    private final Set allCoordinates = new HashSet<>();
    private final Map parentForPom = new HashMap<>();
    private final Map> childPomsByParent = new HashMap<>();

    private final Poms poms;

    public PomCategorizer(Poms poms)
    {
        this.poms = poms;
        categorizePoms();
    }

    public Set families()
    {
        return pomsForFamily.keySet();
    }

    public boolean is(Pom pom, PomRole role)
    {
        Set forPom = rolesForPom.get(pom);
        return forPom == null
               ? false
               : forPom.contains(role);
    }

    public void eachPom(Consumer c)
    {
        allPoms().forEach(c);
    }

    public List allPoms()
    {
        return poms.poms();
    }

    public Map> pomsForValueOfProperty(String property)
    {
        return pomsForValueOfProperty.getOrDefault(property, emptyMap());
    }

    public Map>> pomsForValueOfProperty()
    {
        return pomsForValueOfProperty;
    }

    public Set allCoordinates()
    {
        return allCoordinates;
    }

    public Map> rolesForPom()
    {
        return rolesForPom;
    }

    public Map> pomsForFamily()
    {
        return pomsForFamily;
    }

    public void eachPomInFamily(ProjectFamily family, Consumer c)
    {
        Set result = pomsForFamily.get(family);
        if (result != null)
        {
            result.forEach(c);
        }
    }

    public Map> projectsInFamilyWithRole(PomRole role)
    {
        Map> result = new HashMap<>();
        pomsForFamily.forEach((family, poms) ->
        {
            Set set = poms.stream().filter(pom -> is(pom, role)).collect(
                    Collectors.toCollection(HashSet::new));
            result.put(family, set);
        });
        return result;
    }

    public Set pomsWithRoles(PomRole first, PomRole... more)
    {
        Set result = new HashSet<>();
        eachPomWithRoleIn(result::add, first, more);
        return result;
    }

    public void eachPomWithRoleIn(Consumer c, PomRole first,
            PomRole... more)
    {
        Set all = PomRole.setOf(first, more);
        for (Pom pom : poms)
        {
            Set roles = EnumSet.copyOf(rolesForPom.get(pom));
            roles.retainAll(all);
            if (!roles.isEmpty())
            {
                c.accept(pom);
            }
        }
    }

    public Set pomsForFamily(ProjectFamily family)
    {
        return pomsForFamily.getOrDefault(family, emptySet());
    }

    public Set rolesFor(Pom pom)
    {
        return rolesForPom.getOrDefault(pom, Collections.emptySet());
    }

    public Poms poms()
    {
        return poms;
    }

    public boolean hasParent(Pom pom)
    {
        return parentForPom.containsKey(pom);
    }

    public Optional parentOf(Pom pom)
    {
        return Optional.ofNullable(parentForPom.get(pom));
    }

    public void eachPomAndItsRoles(BiConsumer> c)
    {
        PomRole.visitMapEntriesSorted(rolesForPom, c);
    }

    public ThrowingOptional pomFor(MavenCoordinates coords)
    {
        return poms.get(coords);
    }

    public List parents(Pom what)
    {
        List result = new ArrayList<>();
        while (what != null)
        {
            result.add(what);
            what = parentForPom.get(what);
        }
        return result;
    }

    private void onPom(PomRole role, Pom pom)
    {
        Set pomSet = pomsForKind
                .computeIfAbsent(role, r -> new HashSet<>());
        Set roles = rolesForPom.computeIfAbsent(pom,
                p -> new HashSet<>());
        pomSet.add(pom);
        roles.add(role);
    }

    public Set childrenOf(Pom pom)
    {
        return childPomsByParent.getOrDefault(pom, emptySet());
    }

    public Set descendantsOf(Pom pom)
    {
        Set seen = new HashSet<>();
        Set result = new HashSet<>();
        visitDescendantsOf(pom, seen, p ->
        {
            result.add(p);
            return true;
        });
        return result;
    }

    /**
     * Visit all descendants of a pom, aborting child trees if the passed
     * predicate returns false.
     *
     * @param pom A pom
     * @param addTo A predicate - if it returns false, descendants of the pom
     * passed will not be followed any further
     */
    public void visitDescendantsOf(Pom pom, Predicate addTo)
    {
        visitDescendantsOf(pom, new HashSet<>(), addTo);
    }

    private void visitDescendantsOf(Pom pom, Set seen, Predicate addTo)
    {
        // In a mangled tree with cycles, which it is possible to encounter,
        // we could endlessly loop, so protect against that
        if (seen.contains(pom))
        {
            return;
        }
        seen.add(pom);
        childrenOf(pom).forEach(desc ->
        {
            if (addTo.test(desc))
            {
                visitDescendantsOf(desc, seen, addTo);
            }
        });
    }

    public Set roots()
    {
        Set result = new HashSet<>();
        for (Pom pom : allPoms())
        {
            if (!parentForPom.containsKey(pom))
            {
                result.add(pom);
            }
        }
        return result;
    }

    private void recordParent(Pom child, Pom parent)
    {
        parentForPom.put(child, parent);
        Set all = childPomsByParent.computeIfAbsent(parent,
                p -> new HashSet<>());
        all.add(child);
    }

    private void categorizePoms()
    {
        // Collect the ways each pom is used within the project tree
        Set parents = new HashSet<>();
        for (Pom pom : poms.poms())
        {
            ThrowingOptional parent = pom.parent();
            // Collect the actual parent POM
            parent.ifPresent(par ->
            {
                // Make sure we use the type with the right equality contract
                parents.add(par.toPlainMavenCoordinates());
                poms.get(par).ifPresent(
                        parentPom -> recordParent(pom, parentPom));
            });

            // Make sure 
            allCoordinates.add(pom.coordinates().toPlainMavenCoordinates());
            // Create an inverse index by property by property value to
            // map all POMs defining a property in relation to the name
            // and value
            pom.properties().forEach((prop, value) ->
            {
                Map> pomsByValue
                        = pomsForValueOfProperty.computeIfAbsent(prop,
                                p -> new HashMap<>());
                Set set = pomsByValue.computeIfAbsent(value,
                        v -> new HashSet<>());
                set.add(pom);
            });
            // Collect the family
            ProjectFamily fam = familyOf(pom);
            Set forFamily = pomsForFamily.computeIfAbsent(fam,
                    f -> new HashSet<>());
            forFamily.add(pom);
            // If it is a pom project...
            if (pom.isPomProject())
            {
                if (!pom.modules().isEmpty())
                {
                    // If it has modules, it is a bill of materials.
                    // We'll catch up with whether it is also supplying
                    // configuration below
                    onPom(BILL_OF_MATERIALS, pom);
                }
                else
                {
                    if (!parent.isPresent())
                    {
                        onPom(CONFIG_ROOT, pom);
                    }
                    else
                    {
                        onPom(CONFIG, pom);
                    }
                }
            }
            else
            {
                onPom(JAVA, pom);
            }
        }
        // Now mark all the poms referencecd by other poms as their parent
        // as having that category
        parents.forEach(parent ->
        {
            poms.get(parent).ifPresent(parentPom ->
            {
                onPom(PARENT, parentPom);
            });
        });

        // Note any UFOs so they don't disappear from our univers
        for (Pom pom : poms.poms())
        {
            Set roles = rolesForPom.get(pom);
            if (roles == null || roles.isEmpty())
            {
                onPom(UNKNOWN, pom);
            }
        }
        // If something is a parent AND a BILL_OF_MATERIALS (the typical maven
        // bill-of-materials-is-also-parent layout) then by definition, that
        // POM can also supply configuration to its children, so note that
        rolesForPom.forEach((pom, roles) ->
        {
            if (roles.contains(PARENT) && roles.contains(BILL_OF_MATERIALS))
            {
                if (pom.parent().isPresent())
                {
                    roles.add(CONFIG_ROOT);
                }
                else
                {
                    roles.add(CONFIG);
                }
            }
        });
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy