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 * Example prefix files:
038 * <ul>
039 *     <li><a href="https://repo.maven.apache.org/maven2/.meta/prefixes.txt">Maven Central</a></li>
040 *     <li><a href="https://repository.apache.org/content/repositories/releases/.meta/prefixes.txt">ASF Releases</a></li>
041 *     <li><a href="https://repo.eclipse.org/content/repositories/tycho/.meta/prefixes.txt">Eclipse Tycho</a></li>
042 * </ul>
043 */
044public class PrefixTree extends Node {
045    public static final PrefixTree SENTINEL = new PrefixTree("sentinel");
046
047    private static List<String> elementsOfPath(final String path) {
048        return Arrays.stream(path.split("/")).filter(e -> !e.isEmpty()).collect(toList());
049    }
050
051    public PrefixTree(String name) {
052        super(name, false, null);
053    }
054
055    public int loadNodes(Stream<String> linesStream) {
056        AtomicInteger counter = new AtomicInteger(0);
057        linesStream.forEach(line -> {
058            if (loadNode(line)) {
059                counter.incrementAndGet();
060            }
061        });
062        return counter.get();
063    }
064
065    public boolean loadNode(String line) {
066        if (!line.startsWith("#") && !line.trim().isEmpty()) {
067            Node currentNode = this;
068            for (String element : elementsOfPath(line)) {
069                currentNode = currentNode.addSibling(element, false, null);
070            }
071            return true;
072        }
073        return false;
074    }
075
076    public boolean acceptedPath(String path) {
077        final List<String> pathElements = elementsOfPath(path);
078        Node currentNode = this;
079        for (String pathElement : pathElements) {
080            currentNode = currentNode.getSibling(pathElement);
081            if (currentNode == null || currentNode.isLeaf()) {
082                break;
083            }
084        }
085        return currentNode != null && currentNode.isLeaf();
086    }
087}