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.ArrayList; 022import java.util.Arrays; 023import java.util.List; 024import java.util.concurrent.atomic.AtomicInteger; 025import java.util.stream.Stream; 026 027import static java.util.stream.Collectors.toList; 028 029/** 030 * Group tree for Maven groupIDs. 031 * This class parses a text file that has a directive on each line. Directive examples: 032 * <ul> 033 * <li>ignored/formatting - each line starting with {@code '#'} (hash) or being empty/blank is ignored.</li> 034 * <li>modifier {@code !} is negation (disallow; by def entry allows). If present must be first character.</li> 035 * <li>modifier {@code =} is limiter (to given G; by def is "G and below"). If present, must be first character. If negation present, must be second character.</li> 036 * <li>a valid Maven groupID ie "org.apache.maven".</li> 037 * </ul> 038 * By default, a G entry ie {@code org.apache.maven} means "allow {@code org.apache.maven} G and all Gs below 039 * (so {@code org.apache.maven.plugins} etc. are all allowed). 040 * 041 * Examples: 042 * <pre> 043 * {@code 044 * # this is my group filter list 045 * 046 * org.apache.maven 047 * !=org.apache.maven.foo 048 * !org.apache.maven.indexer 049 * =org.apache.bar 050 * } 051 * </pre> 052 * 053 * File meaning: "allow all {@code org.apache.maven} and below", "disallow {@code org.apache.maven.foo} groupId ONLY" 054 * (hence {@code org.apache.maven.foo.bar} is allowed due first line), "disallow {@code org.apache.maven.indexer} and below" 055 * and "allow {@code org.apache.bar} groupID ONLY". 056 * 057 * <p> 058 * In case of conflicting rules, parsing happens by "first wins", so line closer to first line in file "wins", and conflicting 059 * line is ignored. 060 */ 061public class GroupTree extends Node { 062 public static final GroupTree SENTINEL = new GroupTree("sentinel"); 063 064 private static final String MOD_EXCLUSION = "!"; 065 private static final String MOD_STOP = "="; 066 067 private static List<String> elementsOfGroup(final String groupId) { 068 return Arrays.stream(groupId.split("\\.")).filter(e -> !e.isEmpty()).collect(toList()); 069 } 070 071 public GroupTree(String name) { 072 super(name, false, null); 073 } 074 075 public int loadNodes(Stream<String> linesStream) { 076 AtomicInteger counter = new AtomicInteger(0); 077 linesStream.forEach(line -> { 078 if (loadNode(line)) { 079 counter.incrementAndGet(); 080 } 081 }); 082 return counter.get(); 083 } 084 085 public boolean loadNode(String line) { 086 if (!line.startsWith("#") && !line.trim().isEmpty()) { 087 Node currentNode = this; 088 boolean allow = true; 089 if (line.startsWith(MOD_EXCLUSION)) { 090 allow = false; 091 line = line.substring(MOD_EXCLUSION.length()); 092 } 093 boolean stop = false; 094 if (line.startsWith(MOD_STOP)) { 095 stop = true; 096 line = line.substring(MOD_STOP.length()); 097 } 098 List<String> groupElements = elementsOfGroup(line); 099 for (String groupElement : groupElements.subList(0, groupElements.size() - 1)) { 100 currentNode = currentNode.addSibling(groupElement, false, null); 101 } 102 currentNode.addSibling(groupElements.get(groupElements.size() - 1), stop, allow); 103 return true; 104 } 105 return false; 106 } 107 108 public boolean acceptedGroupId(String groupId) { 109 final List<String> current = new ArrayList<>(); 110 final List<String> groupElements = elementsOfGroup(groupId); 111 Boolean accepted = null; 112 Node currentNode = this; 113 for (String groupElement : groupElements) { 114 current.add(groupElement); 115 currentNode = currentNode.getSibling(groupElement); 116 if (currentNode == null) { 117 break; 118 } 119 if (currentNode.isStop() && groupElements.equals(current)) { 120 accepted = currentNode.isAllow(); 121 } else if (!currentNode.isStop()) { 122 accepted = currentNode.isAllow(); 123 } 124 } 125 return accepted != null && accepted; 126 } 127}