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 }