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 }