001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.internal.impl.filter.ruletree;
020
021import java.util.Arrays;
022import java.util.List;
023import java.util.concurrent.atomic.AtomicInteger;
024import java.util.stream.Stream;
025
026import static java.util.stream.Collectors.toList;
027
028/**
029 * Prefix tree for paths: if you step on a path prefix that exists, you are good to go.
030 * This class parses a text file that has a prefix on each line:
031 * <ul>
032 *     <li>ignored/formatting - each line starting with {@code '#'} (hash) or being empty/blank is ignored.</li>
033 *     <li>a path prefix  ie "/org/apache/maven".</li>
034 * </ul>
035 * By default, artifact is allowed if layout converted path of it has a matching prefix in this file.
036 *
037 * <p>
038 * Example prefix files:
039 * <ul>
040 *     <li><a href="https://repo.maven.apache.org/maven2/.meta/prefixes.txt">Maven Central</a></li>
041 *     <li><a href="https://repository.apache.org/content/repositories/releases/.meta/prefixes.txt">ASF Releases</a></li>
042 *     <li><a href="https://repo.eclipse.org/content/repositories/tycho/.meta/prefixes.txt">Eclipse Tycho</a></li>
043 * </ul>
044 */
045public class PrefixTree extends Node<PrefixTree> {
046    private static List<String> elementsOfPath(final String path) {
047        return Arrays.stream(path.split("/")).filter(e -> !e.isEmpty()).collect(toList());
048    }
049
050    public PrefixTree(String name) {
051        super(name);
052    }
053
054    public int loadNodes(Stream<String> linesStream) {
055        AtomicInteger counter = new AtomicInteger(0);
056        linesStream.forEach(line -> {
057            if (loadNode(line)) {
058                counter.incrementAndGet();
059            }
060        });
061        return counter.get();
062    }
063
064    public boolean loadNode(String line) {
065        if (!line.startsWith("#") && !line.trim().isEmpty()) {
066            PrefixTree currentNode = this;
067            for (String element : elementsOfPath(line)) {
068                currentNode = currentNode.siblings.computeIfAbsent(element, PrefixTree::new);
069            }
070            return true;
071        }
072        return false;
073    }
074
075    public boolean acceptedPath(String path) {
076        final List<String> pathElements = elementsOfPath(path);
077        PrefixTree currentNode = this;
078        for (String pathElement : pathElements) {
079            currentNode = currentNode.siblings.get(pathElement);
080            if (currentNode == null || currentNode.isLeaf()) {
081                break;
082            }
083        }
084        // path is accepted if its elements lead to an existing node that is a leaf node
085        return currentNode != null && currentNode.isLeaf();
086    }
087}