View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.internal.impl.filter.ruletree;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  import java.util.concurrent.atomic.AtomicInteger;
25  import java.util.stream.Stream;
26  
27  import static java.util.stream.Collectors.toList;
28  
29  /**
30   * Group tree for Maven groupIDs.
31   * This class parses a text file that has a directive on each line. Directive examples:
32   * <ul>
33   *     <li>ignored/formatting - each line starting with {@code '#'} (hash) or being empty/blank is ignored.</li>
34   *     <li>modifier {@code !} is negation (disallow; by def entry allows). If present must be first character.</li>
35   *     <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>
36   *     <li>a valid Maven groupID ie "org.apache.maven".</li>
37   * </ul>
38   * By default, a G entry ie {@code org.apache.maven} means "allow {@code org.apache.maven} G and all Gs below
39   * (so {@code org.apache.maven.plugins} etc. are all allowed). There is one special entry {@code "*"} (asterisk)
40   * that means "root" and defines the default acceptance: {@code "*"} means "by default accept" and {@code "!*"}
41   * means "by default deny" (same effect as when this character is not present in file). Use of limiter modifier
42   * on "root" like {@code "=*"} has no effect, is simply ignored.
43   *
44   * <p>
45   * Examples:
46   * <pre>
47   * {@code
48   * # this is my group filter list
49   *
50   * org.apache.maven
51   * !=org.apache.maven.foo
52   * !org.apache.maven.indexer
53   * =org.apache.bar
54   * }
55   * </pre>
56   *
57   * File meaning: "allow all {@code org.apache.maven} and below", "disallow {@code org.apache.maven.foo} groupId ONLY"
58   * (hence {@code org.apache.maven.foo.bar} is allowed due first line), "disallow {@code org.apache.maven.indexer} and below"
59   * and "allow {@code org.apache.bar} groupID ONLY".
60   *
61   * <p>
62   * In case of conflicting rules, parsing happens by "last wins", so line closer to last line in file "wins", and conflicting
63   * line value is lost.
64   */
65  public class GroupTree extends Node<GroupTree> {
66      /**
67       * Creates root, that is special: accept is never null.
68       */
69      public static GroupTree create(String name) {
70          GroupTree result = new GroupTree(name);
71          result.accept = false;
72          return result;
73      }
74  
75      private static final String ROOT = "*";
76      private static final String MOD_EXCLUSION = "!";
77      private static final String MOD_STOP = "=";
78  
79      private static List<String> elementsOfGroup(final String groupId) {
80          return Arrays.stream(groupId.split("\\.")).filter(e -> !e.isEmpty()).collect(toList());
81      }
82  
83      private boolean stop;
84      private Boolean accept;
85  
86      private GroupTree(String name) {
87          super(name);
88      }
89  
90      public int loadNodes(Stream<String> linesStream) {
91          AtomicInteger counter = new AtomicInteger(0);
92          linesStream.forEach(line -> {
93              if (loadNode(line)) {
94                  counter.incrementAndGet();
95              }
96          });
97          return counter.get();
98      }
99  
100     public boolean loadNode(String line) {
101         if (!line.startsWith("#") && !line.trim().isEmpty()) {
102             GroupTree currentNode = this;
103             boolean accept = true;
104             if (line.startsWith(MOD_EXCLUSION)) {
105                 accept = false;
106                 line = line.substring(MOD_EXCLUSION.length());
107             }
108             boolean stop = false;
109             if (line.startsWith(MOD_STOP)) {
110                 stop = true;
111                 line = line.substring(MOD_STOP.length());
112             }
113             if (ROOT.equals(line)) {
114                 this.accept = accept;
115                 return true;
116             }
117             List<String> groupElements = elementsOfGroup(line);
118             for (String groupElement : groupElements.subList(0, groupElements.size() - 1)) {
119                 currentNode = currentNode.siblings.computeIfAbsent(groupElement, GroupTree::new);
120             }
121             String lastElement = groupElements.get(groupElements.size() - 1);
122             currentNode = currentNode.siblings.computeIfAbsent(lastElement, GroupTree::new);
123             currentNode.stop = stop;
124             currentNode.accept = accept;
125             return true;
126         }
127         return false;
128     }
129 
130     public boolean acceptedGroupId(String groupId) {
131         final List<String> current = new ArrayList<>();
132         final List<String> groupElements = elementsOfGroup(groupId);
133         Boolean accepted = null;
134         GroupTree currentNode = this;
135         for (String groupElement : groupElements) {
136             current.add(groupElement);
137             currentNode = currentNode.siblings.get(groupElement);
138             if (currentNode == null) {
139                 // we stepped off the tree; use value we got so far
140                 break;
141             } else if (currentNode.stop && groupElements.equals(current)) {
142                 // exact match
143                 accepted = currentNode.accept;
144                 break;
145             } else if (!currentNode.stop && currentNode.accept != null) {
146                 // "inherit" if not STOP and allow is set; and most probably we loop more
147                 accepted = currentNode.accept;
148             }
149         }
150         // use 'accepted', if defined; otherwise fallback to root (it always has 'allow' set)
151         return accepted != null ? accepted : this.accept;
152     }
153 
154     @Override
155     public String toString() {
156         return (accept != null ? (accept ? "+" : "-") : "?") + (stop ? "=" : "") + name;
157     }
158 
159     @Override
160     public void dump(String prefix) {
161         System.out.println(prefix + this);
162         for (GroupTree node : siblings.values()) {
163             node.dump(prefix + "  ");
164         }
165     }
166 }