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.apache.maven.internal.impl;
20  
21  import java.io.IOException;
22  import java.nio.file.Path;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.StringJoiner;
30  import java.util.function.Predicate;
31  
32  import org.apache.maven.api.JavaPathType;
33  import org.apache.maven.api.PathType;
34  
35  /**
36   * Cache of {@link PathModularization} instances computed for given {@link Path} elements.
37   * The cache is used for avoiding the need to reopen the same files many times when the
38   * same dependency is used for different scope. For example a path used for compilation
39   * is typically also used for tests.
40   */
41  class PathModularizationCache {
42      /**
43       * Module information for each JAR file or output directories.
44       * Cached when first requested to avoid decoding the module descriptors multiple times.
45       *
46       * @see #getModuleInfo(Path)
47       */
48      private final Map<Path, PathModularization> moduleInfo;
49  
50      /**
51       * Whether JAR files are modular. This map is redundant with {@link #moduleInfo},
52       * but cheaper to compute when the module names are not needed.
53       *
54       * @see #getPathType(Path)
55       */
56      private final Map<Path, PathType> pathTypes;
57  
58      /**
59       * Creates an initially empty cache.
60       */
61      PathModularizationCache() {
62          moduleInfo = new HashMap<>();
63          pathTypes = new HashMap<>();
64      }
65  
66      /**
67       * Gets module information for the given JAR file or output directory.
68       * Module descriptors are read when first requested, then cached.
69       */
70      PathModularization getModuleInfo(Path path) throws IOException {
71          PathModularization info = moduleInfo.get(path);
72          if (info == null) {
73              info = new PathModularization(path, true);
74              moduleInfo.put(path, info);
75              pathTypes.put(path, info.getPathType());
76          }
77          return info;
78      }
79  
80      /**
81       * Returns {@link JavaPathType#MODULES} if the given JAR file or output directory is modular.
82       * This is used in heuristic rules for deciding whether to place a dependency on the class-path
83       * or on the module-path when the {@code "jar"} artifact type is used.
84       */
85      private PathType getPathType(Path path) throws IOException {
86          PathType type = pathTypes.get(path);
87          if (type == null) {
88              type = new PathModularization(path, false).getPathType();
89              pathTypes.put(path, type);
90          }
91          return type;
92      }
93  
94      /**
95       * Selects the type of path where to place the given dependency.
96       * This method returns one of the values specified in the given collection.
97       * This method does not handle the patch-module paths, because the patches
98       * depend on which modules have been previously added on the module-paths.
99       *
100      * <p>If the dependency can be a constituent of both the class-path and the module-path,
101      * then the path type is determined by checking if the dependency is modular.</p>
102      *
103      * @param types types of path where a dependency can be placed
104      * @param filter filter the paths accepted by the tool which will consume the path
105      * @param path path to the JAR file or output directory of the dependency
106      * @return where to place the dependency, or an empty value if the placement cannot be determined
107      * @throws IOException if an error occurred while reading module information
108      */
109     Optional<PathType> selectPathType(Set<PathType> types, Predicate<PathType> filter, Path path) throws IOException {
110         PathType selected = null;
111         boolean classes = false;
112         boolean modules = false;
113         boolean unknown = false;
114         boolean processorClasses = false;
115         boolean processorModules = false;
116         for (PathType type : types) {
117             if (filter.test(type)) {
118                 if (JavaPathType.CLASSES.equals(type)) {
119                     classes = true;
120                 } else if (JavaPathType.MODULES.equals(type)) {
121                     modules = true;
122                 } else if (JavaPathType.PROCESSOR_CLASSES.equals(type)) {
123                     processorClasses = true;
124                 } else if (JavaPathType.PROCESSOR_MODULES.equals(type)) {
125                     processorModules = true;
126                 } else {
127                     unknown = true;
128                 }
129                 if (selected == null) {
130                     selected = type;
131                 } else if (unknown) {
132                     // More than one filtered value, and we don't know how to handle at least one of them.
133                     // TODO: add a plugin mechanism for allowing plugin to specify their selection algorithm.
134                     return Optional.empty();
135                 }
136             }
137         }
138         /*
139          * If the dependency can be both on the class-path and the module-path, we need to chose one of these.
140          * The choice done below will overwrite the current `selected` value because the latter is only the
141          * first value encountered in iteration order, which may be random.
142          */
143         if (classes | modules) {
144             if (classes & modules) {
145                 selected = getPathType(path);
146             } else if (classes) {
147                 selected = JavaPathType.CLASSES;
148             } else {
149                 selected = JavaPathType.MODULES;
150             }
151         } else if (processorClasses & processorModules) {
152             selected = getPathType(path);
153             if (JavaPathType.CLASSES.equals(selected)) {
154                 selected = JavaPathType.PROCESSOR_CLASSES;
155             } else if (JavaPathType.MODULES.equals(selected)) {
156                 selected = JavaPathType.PROCESSOR_MODULES;
157             }
158         }
159         return Optional.ofNullable(selected);
160     }
161 
162     /**
163      * If the module-path contains a filename-based auto-module, prepares a warning message.
164      * It is caller's responsibility to send the message to a logger.
165      *
166      * @param modulePaths content of the module path, or {@code null} if none
167      * @return warning message if at least one filename-based auto-module was found
168      * @throws IOException if an error occurred while reading module information
169      */
170     Optional<String> warningForFilenameBasedAutomodules(Collection<Path> modulePaths) throws IOException {
171         if (modulePaths == null) {
172             return Optional.empty();
173         }
174         var automodulesDetected = new ArrayList<String>();
175         for (Path p : modulePaths) {
176             getModuleInfo(p).addIfFilenameBasedAutomodules(automodulesDetected);
177         }
178         if (automodulesDetected.isEmpty()) {
179             return Optional.empty();
180         }
181         var joiner = new StringJoiner(
182                 ", ",
183                 "Filename-based automodules detected on the module-path: ",
184                 "Please don't publish this project to a public artifact repository.");
185         automodulesDetected.forEach(joiner::add);
186         return Optional.of(joiner.toString());
187     }
188 }