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.HashMap;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.Set;
27  import java.util.function.Predicate;
28  
29  import org.apache.maven.api.JavaPathType;
30  import org.apache.maven.api.PathType;
31  
32  /**
33   * Cache of {@link PathModularization} instances computed for given {@link Path} elements.
34   * The cache is used for avoiding the need to reopen the same files many times when the
35   * same dependency is used for different scope. For example a path used for compilation
36   * is typically also used for tests.
37   */
38  class PathModularizationCache {
39      /**
40       * Module information for each JAR file or output directories.
41       * Cached when first requested to avoid decoding the module descriptors multiple times.
42       *
43       * @see #getModuleInfo(Path)
44       */
45      private final Map<Path, PathModularization> moduleInfo;
46  
47      /**
48       * Whether JAR files are modular. This map is redundant with {@link #moduleInfo},
49       * but cheaper to compute when the module names are not needed.
50       *
51       * @see #getPathType(Path)
52       */
53      private final Map<Path, PathType> pathTypes;
54  
55      /**
56       * Creates an initially empty cache.
57       */
58      PathModularizationCache() {
59          moduleInfo = new HashMap<>();
60          pathTypes = new HashMap<>();
61      }
62  
63      /**
64       * Gets module information for the given JAR file or output directory.
65       * Module descriptors are read when first requested, then cached.
66       */
67      PathModularization getModuleInfo(Path path) throws IOException {
68          PathModularization info = moduleInfo.get(path);
69          if (info == null) {
70              info = new PathModularization(path, true);
71              moduleInfo.put(path, info);
72              pathTypes.put(path, info.getPathType());
73          }
74          return info;
75      }
76  
77      /**
78       * Returns {@link JavaPathType#MODULES} if the given JAR file or output directory is modular.
79       * This is used in heuristic rules for deciding whether to place a dependency on the class-path
80       * or on the module-path when the {@code "jar"} artifact type is used.
81       */
82      private PathType getPathType(Path path) throws IOException {
83          PathType type = pathTypes.get(path);
84          if (type == null) {
85              type = new PathModularization(path, false).getPathType();
86              pathTypes.put(path, type);
87          }
88          return type;
89      }
90  
91      /**
92       * Selects the type of path where to place the given dependency.
93       * This method returns one of the values specified in the given collection.
94       * This method does not handle the patch-module paths, because the patches
95       * depend on which modules have been previously added on the module-paths.
96       *
97       * <p>If the dependency can be a constituent of both the class-path and the module-path,
98       * then the path type is determined by checking if the dependency is modular.</p>
99       *
100      * @param types types of path where a dependency can be placed
101      * @param filter filter the paths accepted by the tool which will consume the path
102      * @param path path to the JAR file or output directory of the dependency
103      * @return where to place the dependency, or an empty value if the placement cannot be determined
104      * @throws IOException if an error occurred while reading module information
105      */
106     Optional<PathType> selectPathType(Set<PathType> types, Predicate<PathType> filter, Path path) throws IOException {
107         PathType selected = null;
108         boolean classes = false;
109         boolean modules = false;
110         boolean unknown = false;
111         for (PathType type : types) {
112             if (filter.test(type)) {
113                 if (JavaPathType.CLASSES.equals(type)) {
114                     classes = true;
115                 } else if (JavaPathType.MODULES.equals(type)) {
116                     modules = true;
117                 } else {
118                     unknown = true;
119                 }
120                 if (selected == null) {
121                     selected = type;
122                 } else if (unknown) {
123                     // More than one filtered value, and we don't know how to handle at least one of them.
124                     // TODO: add a plugin mechanism for allowing plugin to specify their selection algorithm.
125                     return Optional.empty();
126                 }
127             }
128         }
129         if (classes & modules) {
130             selected = getPathType(path);
131         }
132         return Optional.ofNullable(selected);
133     }
134 }