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